Skip to content

Instantly share code, notes, and snippets.

@Lu5ck
Created June 11, 2025 20:56
Show Gist options
  • Save Lu5ck/1644c64cc15c1318b932c76ae51810f1 to your computer and use it in GitHub Desktop.
Save Lu5ck/1644c64cc15c1318b932c76ae51810f1 to your computer and use it in GitHub Desktop.
Beyond all Reason widget - Make builder move to next command if current build task is assisted by nano
-- Original by manshanko, modified by timuela to add more options
-- Lu5ck optimized the codes and changed from "force nano to assist" to "start next queue when nano assist"
function widget:GetInfo()
return {
name = "Holo Place",
desc = "Start next queue if any when assisted by nano",
author = "manshanko, timuela, Lu5ck",
date = "2026-06-12",
layer = 2,
enabled = false,
handler = true,
}
end
-- The game runs at synchronized rate of 30 frames per second
-- The widget will check for build assist every X frames as configured here
-- If you want it to be more responsive, you can put a lower value
-- It might reduce performance, especially so if you are using old cpu
local rateLimit = 10
local i18n = Spring.I18N
local GetSelectedUnits = Spring.GetSelectedUnits
local GetUnitDefID = Spring.GetUnitDefID
local GetUnitIsBeingBuilt = Spring.GetUnitIsBeingBuilt
local GetUnitIsBuilding = Spring.GetUnitIsBuilding
local GetUnitCommandCount = Spring.GetUnitCommandCount
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_REMOVE = CMD.REMOVE
local CMD_HOLO_PLACE = 28340
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_ins", "holo_place_30", "holo_place_60", "holo_place_90" }
}
local HOLO_THRESHOLDS = {
[0] = 1, -- off
[1] = 0, -- instant
[2] = 0.3, -- 30%
[3] = 0.6, -- 60%
[4] = 0.9, -- 90%
}
i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[2], "Holo Place off")
i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[3], "Holo Place Ins")
i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[4], "Holo Place 30") -- don't use % sign, it breaks the UI for some reason
i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[5], "Holo Place 60")
i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.params[6], "Holo Place 90")
i18n.set("en.ui.orderMenu." .. CMD_HOLO_PLACE_DESCRIPTION.action .. "_tooltip", "Start next queue if any when assisted")
local BUILDER_DEFS = {}
local NANO_DEFS = {}
local MAX_DISTANCE = 0
local HOLO_PLACERS = {}
-- Compile the list of builder type and nano type
-- Get highest possible nano build range
for unit_def_id, unit_def in pairs(UnitDefs) do
if unit_def.isBuilder and not unit_def.isFactory then
if #unit_def.buildOptions > 0 then
BUILDER_DEFS[unit_def_id] = unit_def.name
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)
local pos = {GetUnitPosition(target_unit_id)}
local units_near = GetUnitsInCylinder(pos[1], pos[3], MAX_DISTANCE, -2)
local unit_ids = {}
for _, id in ipairs(units_near) do
local dist = NANO_DEFS[GetUnitDefID(id)]
if dist ~= nil and target_unit_id ~= id then
if dist > GetUnitSeparation(target_unit_id, id, true) then
unit_ids[#unit_ids + 1] = id
end
end
end
return unit_ids
end
local function removeUnit(unit_id)
if HOLO_PLACERS[unit_id] then
HOLO_PLACERS[unit_id] = nil
end
end
function widget:UnitDestroyed(unitID)
removeUnit(unitID)
end
function widget:UnitTaken(unitID, unitDefID, oldTeamID, newTeam)
removeUnit(unitID)
end
-- Fired when drawing the command GUI
function widget:CommandsChanged()
local ids = GetSelectedUnits()
local highestMode = 0
local added = false
for i=1, #ids do
local def_id = GetUnitDefID(ids[i])
if BUILDER_DEFS[def_id] then
if not added then
local cmds = widgetHandler.customCommands
cmds[#cmds + 1] = CMD_HOLO_PLACE_DESCRIPTION
added = true
end
if HOLO_PLACERS[ids[i]] then
if HOLO_PLACERS[ids[i]].params > highestMode then
highestMode = HOLO_PLACERS[ids[i]].params
end
end
-- Show the highest mode when multiple selected
CMD_HOLO_PLACE_DESCRIPTION.params[1] = highestMode
end
end
end
-- Fired when command pressed
function widget:CommandNotify(cmd_id, cmd_params, cmd_options)
if cmd_id == CMD_HOLO_PLACE then
CMD_HOLO_PLACE_DESCRIPTION.params[1] = cmd_params[1]
-- Track the builders
local ids = GetSelectedUnits()
for i=1, #ids do
if cmd_params[1] == 0 then
removeUnit(ids[i])
else
local def_id = GetUnitDefID(ids[i])
if BUILDER_DEFS[def_id] then
HOLO_PLACERS[ids[i]] = {params = cmd_params[1]}
end
end
end
return true
end
end
function widget:GameFrame(f)
-- Apparently 30 frames per second, we check every X frames
if not (f % rateLimit == 0) then
return
end
for builder_id, builder in pairs(HOLO_PLACERS) do
local targetID = GetUnitIsBuilding(builder_id)
if targetID then
local cmdCounts = GetUnitCommandCount(builder_id)
if cmdCounts > 1 then -- If is the only command in queue, keep building else check progress
local _, buildProcess = GetUnitIsBeingBuilt(targetID)
if buildProcess >= HOLO_THRESHOLDS[builder.params] then
local nt_ids = ntNearUnit(targetID)
for i=1, #nt_ids do -- Check if any nano assisting
local nt_id = nt_ids[i]
local nt_targetID = GetUnitIsBuilding(nt_id)
if nt_targetID then
if nt_targetID == targetID and nt_id ~= builder_id then
local _, _, cmd_tag = GetUnitCurrentCommand(builder_id)
GiveOrderToUnit(builder_id, CMD_REMOVE, cmd_tag, 0)
break
end
end
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment