Created
June 11, 2025 20:56
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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