Guest User

Untitled

a guest
Oct 21st, 2026
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.80 KB | None | 0 0
  1. --------------------------------------------------------------------------------
  2. --------------------------------------------------------------------------------
  3.  
  4. function widget:GetInfo()
  5. return {
  6. name = "Auto Eco Priority",
  7. desc = "While you excess metal, your unfinished Storages are set to High construction priority. " ..
  8. "While you stall energy, your unfinished energy structures (Solar, Wind, Geo, Fusion, Singularity) are. " ..
  9. "They go back to Normal when it ends.",
  10. author = "Thomas",
  11. date = "2026-09-24",
  12. license = "GNU GPL, v2 or later",
  13. layer = 0,
  14. enabled = true,
  15. }
  16. end
  17.  
  18. --------------------------------------------------------------------------------
  19. --------------------------------------------------------------------------------
  20. -- How it works
  21. --
  22. -- Construction priority lives on the nanoframe as well as on the builder, and
  23. -- the frame's own setting wins (unit_priority.lua AllowUnitBuildStep). So the
  24. -- widget sends CMD_PRIORITY to the frames themselves, and every constructor
  25. -- working on them is fed first, whatever its own priority is.
  26. --
  27. -- Excess and stall use the same tests as the economy panel's warnings
  28. -- (gui_chili_economy_panel2.lua): storage minus the 10000 hidden storage,
  29. -- metal above a fraction of it while income beats pull, energy below a
  30. -- fraction of it. Each condition switches off a little past where it switched
  31. -- on, so the spending it triggers doesn't toggle it straight back.
  32. --
  33. -- Only frames at Normal are raised. A frame you set to Low or High yourself is
  34. -- left alone. A raised frame is put back to Normal when the condition ends,
  35. -- unless you changed its priority in the meantime.
  36. --------------------------------------------------------------------------------
  37.  
  38. VFS.Include("LuaRules/Configs/customcmds.h.lua")
  39. local CMD_PRIORITY = CMD_PRIORITY or 34220
  40.  
  41. local spGetTeamResources = Spring.GetTeamResources
  42. local spGetTeamRulesParam = Spring.GetTeamRulesParam
  43. local spGetUnitRulesParam = Spring.GetUnitRulesParam
  44. local spGetUnitDefID = Spring.GetUnitDefID
  45. local spGetUnitIsBeingBuilt = Spring.GetUnitIsBeingBuilt
  46. local spGetTeamUnits = Spring.GetTeamUnits
  47. local spValidUnitID = Spring.ValidUnitID
  48. local spGiveOrderToUnitArray = Spring.GiveOrderToUnitArray
  49. local spGetGameFrame = Spring.GetGameFrame
  50.  
  51. local HIDDEN_STORAGE = 10000 -- LuaRules/Configs/constants.lua
  52. local HYSTERESIS = 0.15 -- a condition ends this far (as a fraction of storage) past where it began
  53. local CHECK_PERIOD = 15 -- frames between economy checks (0.5 s)
  54. local ORDER_GRACE = 150 -- frames before a missing High is read as the player's own change (5 s)
  55.  
  56. local LOW, NORMAL, HIGH = 0, 1, 2
  57.  
  58. --------------------------------------------------------------------------------
  59. -- Which unit defs count
  60.  
  61. local kindByDefID = {} -- [unitDefID] = "storage" | "energy"
  62. for unitDefID = 1, #UnitDefs do
  63. local ud = UnitDefs[unitDefID]
  64. local cp = ud.customParams or {}
  65. local static = ud.isImmobile or (ud.speed or 0) == 0
  66. local builder = (ud.buildSpeed or 0) > 0
  67. if static and not builder then
  68. -- unitdefs_post.lua moves energyMake into customParams.income_energy and zeroes it.
  69. if tonumber(cp.income_energy or 0) > 0 or (ud.energyMake or 0) > 0 or cp.windgen then
  70. kindByDefID[unitDefID] = "energy"
  71. elseif (ud.metalStorage or 0) > 0 then
  72. kindByDefID[unitDefID] = "storage"
  73. end
  74. end
  75. end
  76.  
  77. --------------------------------------------------------------------------------
  78. -- Options
  79.  
  80. options_path = 'Settings/Unit Behaviour/Auto Eco Priority'
  81.  
  82. options_order = {
  83. 'lbl_metal', 'storageOnExcess', 'excessLevel',
  84. 'lbl_energy', 'energyOnStall', 'stallLevel',
  85. 'lbl_general', 'restore', 'announce',
  86. }
  87.  
  88. options = {
  89. lbl_metal = {name = 'Metal excess', type = 'label', noSave = true},
  90. storageOnExcess = {
  91. name = 'Storages to High on metal excess',
  92. desc = 'While your metal storage is nearly full and still filling, unfinished Storages get High construction priority.',
  93. type = 'bool', value = true,
  94. },
  95. excessLevel = {
  96. name = 'Metal excess at storage fill',
  97. desc = 'Metal counts as excessing above this fraction of your storage while income beats spending. The economy panel warns at 0.9.',
  98. type = 'number', value = 0.9, min = 0.5, max = 1, step = 0.02,
  99. },
  100.  
  101. lbl_energy = {name = 'Energy stall', type = 'label', noSave = true},
  102. energyOnStall = {
  103. name = 'Energy structures to High on energy stall',
  104. desc = 'While your energy storage is nearly empty, unfinished Solars, Winds, Geos, Fusions and Singularities get High construction priority.',
  105. type = 'bool', value = true,
  106. },
  107. stallLevel = {
  108. name = 'Energy stall at storage fill',
  109. desc = 'Energy counts as stalling below this fraction of your storage. The economy panel warns at 0.1.',
  110. type = 'number', value = 0.1, min = 0, max = 0.5, step = 0.02,
  111. },
  112.  
  113. lbl_general = {name = 'General', type = 'label', noSave = true},
  114. restore = {
  115. name = 'Back to Normal when it ends',
  116. desc = 'Frames this widget raised return to Normal once the excess or stall is over. Off: they stay High until finished.',
  117. type = 'bool', value = true,
  118. },
  119. announce = {
  120. name = 'Announce in chat',
  121. desc = 'Print a line to the console whenever frames are raised or put back.',
  122. type = 'bool', value = false,
  123. },
  124. }
  125.  
  126. --------------------------------------------------------------------------------
  127. -- State
  128.  
  129. local myTeam
  130. local active = false -- false while spectating
  131.  
  132. local frames = {} -- [unitID] = kind, our unfinished storage / energy structures
  133. local raised = {} -- [unitID] = frame the High order was sent
  134. local condition = {storage = false, energy = false}
  135.  
  136. --------------------------------------------------------------------------------
  137. -- Economy
  138.  
  139. local function MetalExcess(wasOn)
  140. local mCurr, mStor, mPull, mInco, _, _, _, mReci = spGetTeamResources(myTeam, "metal")
  141. if not mCurr then
  142. return false
  143. end
  144. mStor = mStor - HIDDEN_STORAGE
  145. local netMetal = mInco + (mReci or 0) - mPull - (spGetTeamRulesParam(myTeam, "extraMetalPull") or 0)
  146. if mStor <= 1 then
  147. return netMetal > 0 -- no storage: any surplus is excess
  148. end
  149. local fill = mCurr / mStor
  150. local level = options.excessLevel.value
  151. if wasOn then
  152. return fill >= level - HYSTERESIS
  153. end
  154. return netMetal > 0 and fill >= level
  155. end
  156.  
  157. local function EnergyStall(wasOn)
  158. local eCurr, eStor, _, eInco = spGetTeamResources(myTeam, "energy")
  159. if not eCurr then
  160. return false
  161. end
  162. eStor = eStor - HIDDEN_STORAGE
  163. if eStor <= 1 then
  164. -- The economy panel's no-storage rule: energy income below metal income.
  165. local _, _, _, mInco = spGetTeamResources(myTeam, "metal")
  166. local energyIncome = spGetTeamRulesParam(myTeam, "OD_energyIncome") or eInco
  167. return energyIncome < (mInco or 0)
  168. end
  169. local fill = math.min(eCurr, eStor) / eStor
  170. local level = options.stallLevel.value
  171. if wasOn then
  172. return fill < level + HYSTERESIS
  173. end
  174. return fill < level
  175. end
  176.  
  177. --------------------------------------------------------------------------------
  178. -- Orders
  179.  
  180. local function Say(fmt, ...)
  181. if options.announce.value then
  182. Spring.Echo("Auto Eco Priority: " .. string.format(fmt, ...))
  183. end
  184. end
  185.  
  186. local function Priority(unitID)
  187. return spGetUnitRulesParam(unitID, "buildpriority") or NORMAL
  188. end
  189.  
  190. local function Send(list, state)
  191. if #list > 0 then
  192. spGiveOrderToUnitArray(list, CMD_PRIORITY, {state}, 0)
  193. end
  194. end
  195.  
  196. local function Wanted(kind)
  197. if kind == "storage" then
  198. return condition.storage and options.storageOnExcess.value
  199. end
  200. return condition.energy and options.energyOnStall.value
  201. end
  202.  
  203. local function Apply()
  204. local now = spGetGameFrame()
  205. local toHigh, toNormal = {}, {}
  206. local restore = options.restore.value
  207. for unitID, kind in pairs(frames) do
  208. local current = Priority(unitID)
  209. local sentAt = raised[unitID]
  210. if sentAt then
  211. if current ~= HIGH then
  212. if now - sentAt > ORDER_GRACE then
  213. raised[unitID] = nil -- the player changed it: hands off
  214. end
  215. elseif restore and not Wanted(kind) then
  216. toNormal[#toNormal + 1] = unitID
  217. raised[unitID] = nil
  218. end
  219. elseif current == NORMAL and Wanted(kind) then
  220. toHigh[#toHigh + 1] = unitID
  221. raised[unitID] = now
  222. end
  223. end
  224. Send(toHigh, HIGH)
  225. Send(toNormal, NORMAL)
  226. if #toHigh > 0 then
  227. Say("%d frame(s) to High", #toHigh)
  228. end
  229. if #toNormal > 0 then
  230. Say("%d frame(s) back to Normal", #toNormal)
  231. end
  232. end
  233.  
  234. local function RestoreAll()
  235. local list = {}
  236. for unitID in pairs(raised) do
  237. if spValidUnitID(unitID) and Priority(unitID) == HIGH then
  238. list[#list + 1] = unitID
  239. end
  240. end
  241. Send(list, NORMAL)
  242. raised = {}
  243. end
  244.  
  245. --------------------------------------------------------------------------------
  246. -- Tracking
  247.  
  248. local function AddUnit(unitID, unitDefID, unitTeam)
  249. if unitTeam ~= myTeam then
  250. return
  251. end
  252. local kind = kindByDefID[unitDefID or spGetUnitDefID(unitID)]
  253. if kind and spGetUnitIsBeingBuilt(unitID) then
  254. frames[unitID] = kind
  255. end
  256. end
  257.  
  258. local function RemoveUnit(unitID)
  259. frames[unitID] = nil
  260. raised[unitID] = nil
  261. end
  262.  
  263. function widget:UnitCreated(unitID, unitDefID, unitTeam)
  264. AddUnit(unitID, unitDefID, unitTeam)
  265. end
  266.  
  267. function widget:UnitGiven(unitID, unitDefID, newTeam)
  268. AddUnit(unitID, unitDefID, newTeam)
  269. end
  270.  
  271. function widget:UnitTaken(unitID, unitDefID, oldTeam, newTeam)
  272. if newTeam ~= myTeam then
  273. RemoveUnit(unitID)
  274. end
  275. end
  276.  
  277. function widget:UnitFinished(unitID)
  278. RemoveUnit(unitID)
  279. end
  280.  
  281. function widget:UnitDestroyed(unitID)
  282. RemoveUnit(unitID)
  283. end
  284.  
  285. function widget:GameFrame(n)
  286. if not active or n % CHECK_PERIOD ~= 0 then
  287. return
  288. end
  289. local storage = MetalExcess(condition.storage)
  290. local energy = EnergyStall(condition.energy)
  291. if storage ~= condition.storage then
  292. Say(storage and "metal excess" or "metal excess over")
  293. end
  294. if energy ~= condition.energy then
  295. Say(energy and "energy stall" or "energy stall over")
  296. end
  297. condition.storage, condition.energy = storage, energy
  298. if next(frames) then
  299. Apply()
  300. end
  301. end
  302.  
  303. local function Reinitialize()
  304. frames, raised = {}, {}
  305. condition.storage, condition.energy = false, false
  306. myTeam = Spring.GetMyTeamID()
  307. active = not Spring.GetSpectatingState()
  308. if not active then
  309. return
  310. end
  311. for _, unitID in ipairs(spGetTeamUnits(myTeam) or {}) do
  312. AddUnit(unitID, spGetUnitDefID(unitID), myTeam)
  313. end
  314. end
  315.  
  316. function widget:PlayerChanged()
  317. if Spring.GetMyTeamID() ~= myTeam or (not Spring.GetSpectatingState()) ~= active then
  318. if active then
  319. RestoreAll()
  320. end
  321. Reinitialize()
  322. end
  323. end
  324.  
  325. function widget:Initialize()
  326. if not next(kindByDefID) then
  327. Spring.Echo("Auto Eco Priority: no storage or energy unit defs found, disabling.")
  328. widgetHandler:RemoveWidget(widget)
  329. return
  330. end
  331. Reinitialize()
  332. end
  333.  
  334. function widget:Shutdown()
  335. if active and options.restore.value then
  336. RestoreAll()
  337. end
  338. end
  339.  
Advertisement
Add Comment
Please, Sign In to add comment