Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- function widget:GetInfo()
- return {
- name = "Auto Eco Priority",
- desc = "While you excess metal, your unfinished Storages are set to High construction priority. " ..
- "While you stall energy, your unfinished energy structures (Solar, Wind, Geo, Fusion, Singularity) are. " ..
- "They go back to Normal when it ends.",
- author = "Thomas",
- date = "2026-09-24",
- license = "GNU GPL, v2 or later",
- layer = 0,
- enabled = true,
- }
- end
- --------------------------------------------------------------------------------
- --------------------------------------------------------------------------------
- -- How it works
- --
- -- Construction priority lives on the nanoframe as well as on the builder, and
- -- the frame's own setting wins (unit_priority.lua AllowUnitBuildStep). So the
- -- widget sends CMD_PRIORITY to the frames themselves, and every constructor
- -- working on them is fed first, whatever its own priority is.
- --
- -- Excess and stall use the same tests as the economy panel's warnings
- -- (gui_chili_economy_panel2.lua): storage minus the 10000 hidden storage,
- -- metal above a fraction of it while income beats pull, energy below a
- -- fraction of it. Each condition switches off a little past where it switched
- -- on, so the spending it triggers doesn't toggle it straight back.
- --
- -- Only frames at Normal are raised. A frame you set to Low or High yourself is
- -- left alone. A raised frame is put back to Normal when the condition ends,
- -- unless you changed its priority in the meantime.
- --------------------------------------------------------------------------------
- VFS.Include("LuaRules/Configs/customcmds.h.lua")
- local CMD_PRIORITY = CMD_PRIORITY or 34220
- local spGetTeamResources = Spring.GetTeamResources
- local spGetTeamRulesParam = Spring.GetTeamRulesParam
- local spGetUnitRulesParam = Spring.GetUnitRulesParam
- local spGetUnitDefID = Spring.GetUnitDefID
- local spGetUnitIsBeingBuilt = Spring.GetUnitIsBeingBuilt
- local spGetTeamUnits = Spring.GetTeamUnits
- local spValidUnitID = Spring.ValidUnitID
- local spGiveOrderToUnitArray = Spring.GiveOrderToUnitArray
- local spGetGameFrame = Spring.GetGameFrame
- local HIDDEN_STORAGE = 10000 -- LuaRules/Configs/constants.lua
- local HYSTERESIS = 0.15 -- a condition ends this far (as a fraction of storage) past where it began
- local CHECK_PERIOD = 15 -- frames between economy checks (0.5 s)
- local ORDER_GRACE = 150 -- frames before a missing High is read as the player's own change (5 s)
- local LOW, NORMAL, HIGH = 0, 1, 2
- --------------------------------------------------------------------------------
- -- Which unit defs count
- local kindByDefID = {} -- [unitDefID] = "storage" | "energy"
- for unitDefID = 1, #UnitDefs do
- local ud = UnitDefs[unitDefID]
- local cp = ud.customParams or {}
- local static = ud.isImmobile or (ud.speed or 0) == 0
- local builder = (ud.buildSpeed or 0) > 0
- if static and not builder then
- -- unitdefs_post.lua moves energyMake into customParams.income_energy and zeroes it.
- if tonumber(cp.income_energy or 0) > 0 or (ud.energyMake or 0) > 0 or cp.windgen then
- kindByDefID[unitDefID] = "energy"
- elseif (ud.metalStorage or 0) > 0 then
- kindByDefID[unitDefID] = "storage"
- end
- end
- end
- --------------------------------------------------------------------------------
- -- Options
- options_path = 'Settings/Unit Behaviour/Auto Eco Priority'
- options_order = {
- 'lbl_metal', 'storageOnExcess', 'excessLevel',
- 'lbl_energy', 'energyOnStall', 'stallLevel',
- 'lbl_general', 'restore', 'announce',
- }
- options = {
- lbl_metal = {name = 'Metal excess', type = 'label', noSave = true},
- storageOnExcess = {
- name = 'Storages to High on metal excess',
- desc = 'While your metal storage is nearly full and still filling, unfinished Storages get High construction priority.',
- type = 'bool', value = true,
- },
- excessLevel = {
- name = 'Metal excess at storage fill',
- desc = 'Metal counts as excessing above this fraction of your storage while income beats spending. The economy panel warns at 0.9.',
- type = 'number', value = 0.9, min = 0.5, max = 1, step = 0.02,
- },
- lbl_energy = {name = 'Energy stall', type = 'label', noSave = true},
- energyOnStall = {
- name = 'Energy structures to High on energy stall',
- desc = 'While your energy storage is nearly empty, unfinished Solars, Winds, Geos, Fusions and Singularities get High construction priority.',
- type = 'bool', value = true,
- },
- stallLevel = {
- name = 'Energy stall at storage fill',
- desc = 'Energy counts as stalling below this fraction of your storage. The economy panel warns at 0.1.',
- type = 'number', value = 0.1, min = 0, max = 0.5, step = 0.02,
- },
- lbl_general = {name = 'General', type = 'label', noSave = true},
- restore = {
- name = 'Back to Normal when it ends',
- desc = 'Frames this widget raised return to Normal once the excess or stall is over. Off: they stay High until finished.',
- type = 'bool', value = true,
- },
- announce = {
- name = 'Announce in chat',
- desc = 'Print a line to the console whenever frames are raised or put back.',
- type = 'bool', value = false,
- },
- }
- --------------------------------------------------------------------------------
- -- State
- local myTeam
- local active = false -- false while spectating
- local frames = {} -- [unitID] = kind, our unfinished storage / energy structures
- local raised = {} -- [unitID] = frame the High order was sent
- local condition = {storage = false, energy = false}
- --------------------------------------------------------------------------------
- -- Economy
- local function MetalExcess(wasOn)
- local mCurr, mStor, mPull, mInco, _, _, _, mReci = spGetTeamResources(myTeam, "metal")
- if not mCurr then
- return false
- end
- mStor = mStor - HIDDEN_STORAGE
- local netMetal = mInco + (mReci or 0) - mPull - (spGetTeamRulesParam(myTeam, "extraMetalPull") or 0)
- if mStor <= 1 then
- return netMetal > 0 -- no storage: any surplus is excess
- end
- local fill = mCurr / mStor
- local level = options.excessLevel.value
- if wasOn then
- return fill >= level - HYSTERESIS
- end
- return netMetal > 0 and fill >= level
- end
- local function EnergyStall(wasOn)
- local eCurr, eStor, _, eInco = spGetTeamResources(myTeam, "energy")
- if not eCurr then
- return false
- end
- eStor = eStor - HIDDEN_STORAGE
- if eStor <= 1 then
- -- The economy panel's no-storage rule: energy income below metal income.
- local _, _, _, mInco = spGetTeamResources(myTeam, "metal")
- local energyIncome = spGetTeamRulesParam(myTeam, "OD_energyIncome") or eInco
- return energyIncome < (mInco or 0)
- end
- local fill = math.min(eCurr, eStor) / eStor
- local level = options.stallLevel.value
- if wasOn then
- return fill < level + HYSTERESIS
- end
- return fill < level
- end
- --------------------------------------------------------------------------------
- -- Orders
- local function Say(fmt, ...)
- if options.announce.value then
- Spring.Echo("Auto Eco Priority: " .. string.format(fmt, ...))
- end
- end
- local function Priority(unitID)
- return spGetUnitRulesParam(unitID, "buildpriority") or NORMAL
- end
- local function Send(list, state)
- if #list > 0 then
- spGiveOrderToUnitArray(list, CMD_PRIORITY, {state}, 0)
- end
- end
- local function Wanted(kind)
- if kind == "storage" then
- return condition.storage and options.storageOnExcess.value
- end
- return condition.energy and options.energyOnStall.value
- end
- local function Apply()
- local now = spGetGameFrame()
- local toHigh, toNormal = {}, {}
- local restore = options.restore.value
- for unitID, kind in pairs(frames) do
- local current = Priority(unitID)
- local sentAt = raised[unitID]
- if sentAt then
- if current ~= HIGH then
- if now - sentAt > ORDER_GRACE then
- raised[unitID] = nil -- the player changed it: hands off
- end
- elseif restore and not Wanted(kind) then
- toNormal[#toNormal + 1] = unitID
- raised[unitID] = nil
- end
- elseif current == NORMAL and Wanted(kind) then
- toHigh[#toHigh + 1] = unitID
- raised[unitID] = now
- end
- end
- Send(toHigh, HIGH)
- Send(toNormal, NORMAL)
- if #toHigh > 0 then
- Say("%d frame(s) to High", #toHigh)
- end
- if #toNormal > 0 then
- Say("%d frame(s) back to Normal", #toNormal)
- end
- end
- local function RestoreAll()
- local list = {}
- for unitID in pairs(raised) do
- if spValidUnitID(unitID) and Priority(unitID) == HIGH then
- list[#list + 1] = unitID
- end
- end
- Send(list, NORMAL)
- raised = {}
- end
- --------------------------------------------------------------------------------
- -- Tracking
- local function AddUnit(unitID, unitDefID, unitTeam)
- if unitTeam ~= myTeam then
- return
- end
- local kind = kindByDefID[unitDefID or spGetUnitDefID(unitID)]
- if kind and spGetUnitIsBeingBuilt(unitID) then
- frames[unitID] = kind
- end
- end
- local function RemoveUnit(unitID)
- frames[unitID] = nil
- raised[unitID] = nil
- end
- function widget:UnitCreated(unitID, unitDefID, unitTeam)
- AddUnit(unitID, unitDefID, unitTeam)
- end
- function widget:UnitGiven(unitID, unitDefID, newTeam)
- AddUnit(unitID, unitDefID, newTeam)
- end
- function widget:UnitTaken(unitID, unitDefID, oldTeam, newTeam)
- if newTeam ~= myTeam then
- RemoveUnit(unitID)
- end
- end
- function widget:UnitFinished(unitID)
- RemoveUnit(unitID)
- end
- function widget:UnitDestroyed(unitID)
- RemoveUnit(unitID)
- end
- function widget:GameFrame(n)
- if not active or n % CHECK_PERIOD ~= 0 then
- return
- end
- local storage = MetalExcess(condition.storage)
- local energy = EnergyStall(condition.energy)
- if storage ~= condition.storage then
- Say(storage and "metal excess" or "metal excess over")
- end
- if energy ~= condition.energy then
- Say(energy and "energy stall" or "energy stall over")
- end
- condition.storage, condition.energy = storage, energy
- if next(frames) then
- Apply()
- end
- end
- local function Reinitialize()
- frames, raised = {}, {}
- condition.storage, condition.energy = false, false
- myTeam = Spring.GetMyTeamID()
- active = not Spring.GetSpectatingState()
- if not active then
- return
- end
- for _, unitID in ipairs(spGetTeamUnits(myTeam) or {}) do
- AddUnit(unitID, spGetUnitDefID(unitID), myTeam)
- end
- end
- function widget:PlayerChanged()
- if Spring.GetMyTeamID() ~= myTeam or (not Spring.GetSpectatingState()) ~= active then
- if active then
- RestoreAll()
- end
- Reinitialize()
- end
- end
- function widget:Initialize()
- if not next(kindByDefID) then
- Spring.Echo("Auto Eco Priority: no storage or energy unit defs found, disabling.")
- widgetHandler:RemoveWidget(widget)
- return
- end
- Reinitialize()
- end
- function widget:Shutdown()
- if active and options.restore.value then
- RestoreAll()
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment