Skip to content

Instantly share code, notes, and snippets.

@Lu5ck
Created June 11, 2025 20:56
Show Gist options
  • Select an option

  • Save Lu5ck/1644c64cc15c1318b932c76ae51810f1 to your computer and use it in GitHub Desktop.

Select an option

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
@Lu5ck
Copy link
Author

Lu5ck commented Sep 2, 2025

There is a known bug with grid where you right click it, it will turn off but the gui will not change. I do not know how to hook that right click event. It doesn't seem hookable either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment