function widget:GetInfo() return { name = "Holo Place", desc = "Start next building while nearby nano turrets are free", author = "manshanko", date = "2025-04-14", home = "https://github.com/manshanko/bar-widgets", layer = 2, handler = true, } end if Spring.GetSpectatingState() then return end local echo = Spring.Echo local i18n = Spring.I18N local GetSelectedUnits = Spring.GetSelectedUnits local GetUnitBuildeeRadius = Spring.GetUnitBuildeeRadius local GetUnitCommandCount = Spring.GetUnitCommandCount local GetUnitDefID = Spring.GetUnitDefID local GetUnitIsBeingBuilt = Spring.GetUnitIsBeingBuilt local GetUnitIsBuilding = Spring.GetUnitIsBuilding local GetUnitCommands = Spring.GetUnitCommands local GetUnitCurrentCommand = Spring.GetUnitCurrentCommand local GetUnitPosition = Spring.GetUnitPosition local GetUnitSeparation = Spring.GetUnitSeparation local GetUnitsInCylinder = Spring.GetUnitsInCylinder local GiveOrderToUnit = Spring.GiveOrderToUnit local UnitDefs = UnitDefs local CMD_REPAIR = CMD.REPAIR local CMD_REMOVE = CMD.REMOVE local CMD_FIGHT = CMD.FIGHT local CMD_HOLO_PLACE = 28339 local CMD_HOLO_PLACE_DESCRIPTION = { id = CMD_HOLO_PLACE, type = CMDTYPE.ICON_MODE, name = "Holo Place", cursor = nil, action = "holo_place", params = { 0, "holo_place_off", "holo_place_on" } } i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[2], "Holo Place") i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[3], "Holo Place") i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.action .. "_tooltip", "Start next building while nearby nano turrets are free") local BUILDER_DEFS = {} local NANO_DEFS = {} local BT_DEFS = {} local MAX_DISTANCE = 0 local HOLO_PLACERS = {} for unit_def_id, unit_def in pairs(UnitDefs) do BT_DEFS[unit_def_id] = unit_def.buildTime if unit_def.isBuilder and not unit_def.isFactory then if #unit_def.buildOptions > 0 then BUILDER_DEFS[unit_def_id] = unit_def.buildSpeed end if not unit_def.canMove then NANO_DEFS[unit_def_id] = unit_def.buildDistance if unit_def.buildDistance > MAX_DISTANCE then MAX_DISTANCE = unit_def.buildDistance end end end end local function ntNearUnit(target_unit_id, callback) local buildee_radius = GetUnitBuildeeRadius(target_unit_id) - 1 local x, y, z = GetUnitPosition(target_unit_id) local units_near = GetUnitsInCylinder(x, z, MAX_DISTANCE + buildee_radius, -2) for i=1, #units_near do local id = units_near[i] if not GetUnitIsBeingBuilt(id) then local dist = NANO_DEFS[GetUnitDefID(id)] if dist ~= nil and target_unit_id ~= id then if dist > GetUnitSeparation(target_unit_id, id, true, true) then if callback(id) then return end end end end end end local function checkUnits(update) local builders = {} local has_holo_place = false local ids = GetSelectedUnits() for i=1, #ids do local unit_id = ids[i] local def_id = GetUnitDefID(unit_id) if BUILDER_DEFS[def_id] then if HOLO_PLACERS[ids[i]] then has_holo_place = true end builders[#builders + 1] = unit_id end end if #builders > 0 then if update then if has_holo_place then for i=1, #builders do HOLO_PLACERS[builders[i]] = nil end has_holo_place = false else for i=1, #builders do HOLO_PLACERS[builders[i]] = HOLO_PLACERS[builders[i]] or {} end has_holo_place = true end end if has_holo_place then CMD_HOLO_PLACE_DESCRIPTION.params[1] = 1 else CMD_HOLO_PLACE_DESCRIPTION.params[1] = 0 end return true end end local function handleHoloPlace() checkUnits(true) end function widget:MetaUnitRemoved(unit_id) HOLO_PLACERS[unit_id] = nil end function widget:CommandsChanged() if checkUnits(false) then local cmds = widgetHandler.customCommands cmds[#cmds + 1] = CMD_HOLO_PLACE_DESCRIPTION end end function widget:CommandNotify(cmd_id, cmd_params, cmd_options) if cmd_id == CMD_HOLO_PLACE then checkUnits(true) return true end end function widget:GameFrame() for unit_id, builder in pairs(HOLO_PLACERS) do local target_id = GetUnitIsBuilding(unit_id) if builder.nt_id and target_id == builder.building_id then local building_id = GetUnitIsBuilding(builder.nt_id) local num_cmds = GetUnitCommands(builder.nt_id, 0) if building_id == builder.building_id and num_cmds == 1 then builder.nt_id = false GiveOrderToUnit(unit_id, CMD_REMOVE, builder.cmd_tag, 0) elseif builder.tick > 30 then builder.nt_id = false builder.building_id = false else builder.tick = builder.tick + 1 end elseif target_id and target_id ~= builder.building_id then -- find nearby nano to pin holo ntNearUnit(target_id, function(nt_id) local cmds = GetUnitCommands(nt_id, 2) if (cmds[2] and cmds[2].id == CMD_FIGHT) or (cmds[1] and cmds[1].id == CMD_FIGHT) then local _, _, tag = GetUnitCurrentCommand(unit_id) builder.nt_id = nt_id builder.tick = 0 builder.building_id = target_id builder.cmd_tag = tag GiveOrderToUnit(nt_id, CMD_REPAIR, target_id, 0) return true end end) end end end function widget:Initialize() widgetHandler.actionHandler:AddAction(self, "holo_place", handleHoloPlace, nil, "p") end function widget:Shutdown() widgetHandler.actionHandler:RemoveAction(self, "holo_place", "p") end