-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsmart_turrets.lua
More file actions
332 lines (295 loc) · 12.2 KB
/
Copy pathsmart_turrets.lua
File metadata and controls
332 lines (295 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
function widget:GetInfo()
return {
name = "Smart Construction Turret",
desc = "Forces nano turrets to assist based on your energy and metal situation",
author = "augustin",
date = "2025-06-15",
layer = 0,
enabled = true,
}
end
--------------------------------------------------------------------------------
-- CONFIGURATION: List of all nano turrets and energy converters from units.json
--------------------------------------------------------------------------------
local TURRET_NAMES = {
"armnanotc", "armnanotcplat", "armnanotct2", "armnanotc2plat", "armrespawn",
"cornanotc", "cornanotcplat", "cornanotct2", "cornanotc2plat", "correspawn",
"legnanotc", "legnanotcplat", "legnanotct2", "legnanotct2plat", "legnanotcbase",
"armnanotct3", "cornanotct3", "legnanotct3",
}
local CONVERTER_NAMES = {
"armmakr", "armmmkr", "armmmkrt3", "armfmkr",
"cormakr", "cormmkr", "cormmkrt3", "corfmkr",
"legfeconv", "legeconv", "legadveconv", "legadveconvt3", "legfmkr"
}
local ENERGY_NAMES = {
-- Fusion Reactors
"armafus", "armafust3", "armckfus", "armdf",
"corafus", "corafust3", "corckfus", "corfus", "coruwfus", "cordf",
"legafus", "legafust3", "legfus", "freefusion",
-- Wind Turbines
"armwin", "armwint2",
"corwin", "corwint2",
"legwin", "legwint2",
-- Solar Collectors
"armadvsol", "armsolar",
"coradvsol", "corsolar",
"legadvsol", "legsolar"
}
-- Convert names to UnitDefIDs for fast lookup
local TURRET_DEF_IDS = {}
local CONVERTER_DEF_IDS = {}
local REACTOR_DEF_IDS = {}
for _, name in ipairs(TURRET_NAMES) do
local def = UnitDefNames[name]
if def then TURRET_DEF_IDS[def.id] = true end
end
for _, name in ipairs(CONVERTER_NAMES) do
local def = UnitDefNames[name]
if def then CONVERTER_DEF_IDS[def.id] = true end
end
for _, name in ipairs(ENERGY_NAMES) do
local def = UnitDefNames[name]
if def then REACTOR_DEF_IDS[def.id] = true end
end
--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------
local function distance2D(x1, z1, x2, z2)
local dx, dz = x1 - x2, z1 - z2
return math.sqrt(dx*dx + dz*dz)
end
-- Helper to get converter usage percent
local function GetConverterUsagePercent()
local myTeamID = Spring.GetMyTeamID()
local eConverted = Spring.GetTeamRulesParam(myTeamID, "mmUse") or 0
local eConvertedMax = Spring.GetTeamRulesParam(myTeamID, "mmCapacity") or 0
if eConvertedMax <= 0 then return 0 end
return math.floor(100 * eConverted / eConvertedMax)
end
-- Helper to get metal storage percent
local function GetMetalStoragePercent()
local myTeamID = Spring.GetMyTeamID()
local metal, metalStorage = Spring.GetTeamResources(myTeamID, "metal")
if not metal or not metalStorage or metalStorage == 0 then return 0 end
return (metal / metalStorage) * 100
end
-- Spring.Echo("Metal storage percent:", GetMetalStoragePercent())
-- Helper to get energy storage percent
local function GetEnergyStoragePercent()
local myTeamID = Spring.GetMyTeamID()
local energy, energyStorage = Spring.GetTeamResources(myTeamID, "energy")
if not energy or not energyStorage or energyStorage == 0 then return 0 end
return (energy / energyStorage) * 100
end
-- Spring.Echo("Energy storage percent:", GetEnergyStoragePercent())
--------------------------------------------------------------------------------
-- MAIN LOGIC
--------------------------------------------------------------------------------
local lastUpdateTime = 0
local updateInterval = 1
local converterFullStreak = 0
local watchedBuildTurrets = {}
local function hasEmptyBuildQueue(unitID)
local cmds = Spring.GetUnitCommands(unitID, 2)
if not cmds or #cmds == 0 then
return true
end
if #cmds == 1 and (cmds[1].id == CMD.FIGHT or cmds[1].id == CMD.STOP) then
return true
end
return false
end
local function isBuildTurret(unitID)
local defID = Spring.GetUnitDefID(unitID)
if TURRET_DEF_IDS[defID] then
return true
end
return false
end
function widget:UnitCmdDone(unitID)
if hasEmptyBuildQueue(unitID) and isBuildTurret(unitID) then
watchedBuildTurrets[unitID] = true
end
end
function widget:CommandNotify(cmdID, cmdParams, cmdOpts)
selectedUnits = Spring.GetSelectedUnits()
for _,orderedUnit in ipairs(selectedUnits) do
if isBuildTurret(orderedUnit) then
watchedBuildTurrets[orderedUnit] = nil
end
end
end
function widget:Initialize()
local myTeamID = Spring.GetMyTeamID()
for _, unitID in ipairs(Spring.GetTeamUnits(myTeamID)) do
if isBuildTurret(unitID) and hasEmptyBuildQueue(unitID) then
watchedBuildTurrets[unitID] = true
end
end
end
function widget:UnitFinished(unitID, unitDefID, unitTeam)
if unitTeam == Spring.GetMyTeamID() and isBuildTurret(unitID) and hasEmptyBuildQueue(unitID) then
watchedBuildTurrets[unitID] = true
end
end
function widget:GameFrame(n)
local now = Spring.GetTimer()
if lastUpdateTime == 0 then
lastUpdateTime = now
end
local elapsed = Spring.DiffTimers(now, lastUpdateTime)
if elapsed < updateInterval then
return
end
lastUpdateTime = now
-- Get converter usage percent
local converterUsage = GetConverterUsagePercent()
-- Track streak of 100% usage
if converterUsage >= 100 then
converterFullStreak = converterFullStreak + 1
else
converterFullStreak = 0
end
-- Find all converters under construction
local converters = {}
local myTeamID = Spring.GetMyTeamID()
for _, unitID in ipairs(Spring.GetTeamUnits(myTeamID)) do
local defID = Spring.GetUnitDefID(unitID)
if CONVERTER_DEF_IDS[defID] then
local unitTeam = Spring.GetUnitTeam(unitID)
if unitTeam == myTeamID then
local x, _, z = Spring.GetUnitPosition(unitID)
local _, _, _, _, build = Spring.GetUnitHealth(unitID)
if build and build < 1 then
converters[#converters+1] = {id=unitID, x=x, z=z}
end
end
end
end
-- Find all reactors under construction
local reactors = {}
for _, unitID in ipairs(Spring.GetTeamUnits(myTeamID)) do
local defID = Spring.GetUnitDefID(unitID)
if REACTOR_DEF_IDS[defID] then
local unitTeam = Spring.GetUnitTeam(unitID)
if unitTeam == myTeamID then
local x, _, z = Spring.GetUnitPosition(unitID)
local _, _, _, _, build = Spring.GetUnitHealth(unitID)
if build and build < 1 then
reactors[#reactors+1] = {id=unitID, x=x, z=z}
end
end
end
end
-- Find all turrets under construction
local turrets = {}
for _, unitID in ipairs(Spring.GetTeamUnits(myTeamID)) do
local defID = Spring.GetUnitDefID(unitID)
if TURRET_DEF_IDS[defID] then
local unitTeam = Spring.GetUnitTeam(unitID)
if unitTeam == myTeamID then
local x, _, z = Spring.GetUnitPosition(unitID)
local _, _, _, _, build = Spring.GetUnitHealth(unitID)
if build and build < 1 then
turrets[#turrets+1] = {id=unitID, x=x, z=z}
end
end
end
end
-- Check metal storage percent
local metalStoragePercent = GetMetalStoragePercent()
local energyStoragePercent = GetEnergyStoragePercent()
-- Decide targets based on converter usage
local CONVERTER_STABLE_TIME = 2 -- seconds of stable 100% usage to switch to converters
local ENERGY_STORAGE_THRESHOLD = 30 -- percent energy storage to consider converters stable
local targets = nil
-- Decide which type of building (reactors, turrets, converters) the idle nano turrets should assist
if energyStoragePercent < 5 and #reactors > 0 then
-- If energy storage is critically low (<5%)
-- and there are turrets under construction,
-- prioritize finishing turrets to defend yourself quickly.
targets = reactors
elseif metalStoragePercent > 10 and #turrets > 0 then
-- If you have more than 10% metal stored
-- and there are turrets being built,
-- use the extra metal to rush turret construction (defense).
targets = turrets
elseif converterUsage < 100 and #reactors > 0 then
-- If metal-to-energy converters are not running at full capacity (<100%)
-- and reactors are under construction,
-- focus nano turrets on helping reactors to increase energy income.
targets = reactors
elseif converterFullStreak * updateInterval >= CONVERTER_STABLE_TIME and energyStoragePercent >= ENERGY_STORAGE_THRESHOLD and #converters > 0 then
-- If converters have been fully saturated for a stable period (e.g. 2s),
-- AND you have enough energy in storage (>=30% by default),
-- AND converters are under construction,
-- then prioritize finishing converters to expand conversion capacity.
targets = converters
end
if not targets or #targets == 0 then return end
-- For each nano turret, check for in-range targets and force assist
for _, unitID in ipairs(Spring.GetTeamUnits(Spring.GetMyTeamID())) do
local defID = Spring.GetUnitDefID(unitID)
if TURRET_DEF_IDS[defID] and watchedBuildTurrets[unitID] then
local ux, _, uz = Spring.GetUnitPosition(unitID)
local buildRange = UnitDefs[defID].buildDistance or 300
-- Gather all in-range targets
local inRangeTargets = {}
for _, tgt in ipairs(targets) do
local dist = distance2D(ux, uz, tgt.x, tgt.z)
-- Exclude if being reclaimed
local cmds = Spring.GetUnitCommands(tgt.id, 10)
local beingReclaimed = false
for _, cmd in ipairs(cmds) do
if cmd.id == CMD.RECLAIM then
beingReclaimed = true
break
end
end
if not beingReclaimed and dist <= buildRange + (UnitDefs[Spring.GetUnitDefID(tgt.id)].radius or 0) then
table.insert(inRangeTargets, tgt)
end
end
if #inRangeTargets > 0 then
-- Check if already assisting/building any in-range target, or reclaiming or resurrecting something
local cmds = Spring.GetUnitCommands(unitID, 10)
local already = false
local reclaiming = false
local resurrecting = false
for _, cmd in ipairs(cmds) do
if cmd.id == CMD.RECLAIM then
reclaiming = true
break
end
if cmd.id == CMD.RESURRECT then
resurrecting = true
break
end
if cmd.id == CMD.REPAIR or cmd.id == CMD.GUARD or cmd.id == CMD.BUILD then
for _, tgt in ipairs(inRangeTargets) do
if cmd.params[1] == tgt.id then
already = true
break
end
end
end
if already then break end
end
if not already and not reclaiming and not resurrecting then
-- Pick the closest in-range target to assist
local closest = inRangeTargets[1]
local minDist = distance2D(ux, uz, closest.x, closest.z)
for i = 2, #inRangeTargets do
local d = distance2D(ux, uz, inRangeTargets[i].x, inRangeTargets[i].z)
if d < minDist then
closest = inRangeTargets[i]
minDist = d
end
end
Spring.GiveOrderToUnit(unitID, CMD.REPAIR, {closest.id}, {})
end
end
end
end
end