-
Notifications
You must be signed in to change notification settings - Fork 1
/
build_queue_fast_forward.lua
747 lines (624 loc) · 23.5 KB
/
build_queue_fast_forward.lua
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
function widget:GetInfo()
return {
name = "build queue fast forward",
desc = "Lots of code from gui_build_costs.lua by Milan Satala and also some from ecostats.lua by Jools, iirc",
author = "-",
date = "feb, 2016",
license = "GNU GPL, v2 or later",
layer = 99,
enabled = true
}
end
local GetAllyTeamList = Spring.GetAllyTeamList
local GetMyTeamID = Spring.GetMyTeamID
local GetSelectedUnits = Spring.GetSelectedUnits
local GetTeamResources = Spring.GetTeamResources
local GetTeamResources = Spring.GetTeamResources
local GetTeamRulesParam = Spring.GetTeamRulesParam
local GetTeamRulesParam = Spring.GetTeamRulesParam
local GetTeamUnits = Spring.GetTeamUnits
local GetUnitCommands = Spring.GetUnitCommands
local GetUnitDefID = Spring.GetUnitDefID
local GetUnitHealth = Spring.GetUnitHealth
local GetUnitIsBuilding = Spring.GetUnitIsBuilding
local GetUnitPosition = Spring.GetUnitPosition
local GetUnitResources = Spring.GetUnitResources
local GetUnitsInCylinder = Spring.GetUnitsInCylinder
local GetUnitsInSphere = Spring.GetUnitsInSphere
local GiveOrderToUnit = Spring.GiveOrderToUnit
local UnitDefs = UnitDefs
local abandonedTargetIDs = {}
local builders = {}
local conversionLevelHistory = {}
local log = Spring.Echo
local mainIterationModuloLimit = 5
local regularizedResourceDerivativesMetal = {true}
local regularizedResourceDerivativesEnergy = {true}
local metalMakers = {}
local myTeamId = Spring.GetMyTeamID()
local possibleMetalMakersProduction = 0
local possibleMetalMakersUpkeep = 0
local releasedMetal = 0
local selectedUnits
local t0 = Spring.GetTimer()
local tidalStrength = Game.tidal
local totalSavedTime = 0
local willStall = false
local windMax = Game.windMax
local windMin = Game.windMin
local regularizedPositiveMetal = true
local regularizedPositiveEnergy = true
local regularizedNegativeMetal = false
local regularizedNegativeEnergy = false
local positiveMMLevel = false
local metalLevel = 0.5
local energyLevel = 0.5
local metalMakingLevel = 0.5
local isPositiveMetalDerivative = false
local isPositiveEnergyDerivative = false
local isMetalStalling = false
local isEnergyStalling = false
local isMetalLeaking = true
local isEnergyLeaking = true
function widget:Initialize()
if Spring.GetSpectatingState() or Spring.IsReplay() then
widgetHandler:RemoveWidget()
end
local myUnits = GetTeamUnits(myTeamId)
for _, unitID in ipairs(myUnits) do
local unitDefID = GetUnitDefID(unitID)
registerUnit(unitID, unitDefID, teamID)
end
end
function registerUnit(unitID, unitDefID)
if not unitDefID then
return
end
local unitDef = UnitDefs[unitDefID]
if unitDef.isBuilder and unitDef.canAssist then
builders[unitID] = {["buildSpeed"] = unitDef.buildSpeed, originalBuildSpeed = unitDef.buildSpeed, ['unitDef'] = unitDef, ["targetId"] = nil, ["guards"] = {},
['previousBuilding'] = nil}
end
end
function widget:UnitCreated(unitID, unitDefID, unitTeam)
registerUnit(unitID, unitDefID, unitTeam)
end
function widget:UnitGiven(unitID, unitDefID, unitTeam, oldTeam)
if unitTeam == myTeamId then
registerUnit(unitID, unitDefID)
end
end
function getBuildersBuildSpeed(tempBuilders)
local totalSpeed = 0
for _, unitID in pairs(tempBuilders) do
local targetId = builders[unitID].targetId
if not targetId or not isAlreadyInTable(targetId, tempBuilders) then
totalSpeed = totalSpeed + builders[unitID].buildSpeed
end
end
return totalSpeed
end
function getBuildTimeLeft(unitID)
local _, _, _, _, build = GetUnitHealth(unitID)
local currentBuildSpeed = 0
for builderId, _ in pairs(builders) do
local targetId = GetUnitIsBuilding(builderId)
if targetId == unitID and builderId ~= unitID then
currentBuildSpeed = currentBuildSpeed + builders[builderId].originalBuildSpeed
end
end
local unitDef = UnitDefs[GetUnitDefID(unitID)]
local buildLeft = (1 - build) * unitDef.buildTime
local time = buildLeft / currentBuildSpeed
return time
end
function getUnitsBuildingUnit(unitID)
local building = {}
for builderId, _ in pairs(builders) do
local targetId = GetUnitIsBuilding(builderId)
if targetId == unitID then
building[builderId] = builderId
end
end
return building
end
function widget:GameFrame(n)
if n % mainIterationModuloLimit == 0 then
builderIteration(n)
end
if n % 1000 == 0 then
for k, v in pairs(abandonedTargetIDs) do
local _, _, _, _, build = GetUnitHealth(k)
if build == nil or build == 1 then
table.remove(abandonedTargetIDs, k)
end
end
end
end
function builderIteration(n)
for builderId, _ in pairs(builders) do
local targetId = GetUnitIsBuilding(builderId)
local cmdQueue = GetUnitCommands(builderId, 3)
-- dont wait if has queued stuff
if cmdQueue and #cmdQueue > 0 and cmdQueue[1].id == 5 and (isMetalLeaking or isEnergyLeaking) then
GiveOrderToUnit(builderId, CMD.REMOVE, {nil}, {"ctrl"})
end
if targetId then
-- target id recieved
local builderDef = UnitDefs[GetUnitDefID(builderId)]
local targetDefID = GetUnitDefID(targetId)
local targetDef = UnitDefs[targetDefID]
table.insert(regularizedResourceDerivativesMetal, 1, isPositiveMetalDerivative)
table.insert(regularizedResourceDerivativesEnergy, 1, isPositiveEnergyDerivative)
if table.getn(regularizedResourceDerivativesMetal) > 7 then
table.remove(regularizedResourceDerivativesMetal)
table.remove(regularizedResourceDerivativesEnergy)
end
regularizedPositiveMetal = table.full_of(regularizedResourceDerivativesMetal, true)
regularizedPositiveEnergy = table.full_of(regularizedResourceDerivativesEnergy, true)
regularizedNegativeMetal = table.full_of(regularizedResourceDerivativesMetal, false)
regularizedNegativeEnergy = table.full_of(regularizedResourceDerivativesEnergy, false)
updateFastResourceStatus()
-- queue fast forwarder
if cmdQueue then
cmdQueue = purgeCompleteRepairs(builderId, cmdQueue)
if #cmdQueue > 2 and cmdQueue[3].id < 0 then
-- next command is build command
if not abandonedTargetIDs[targetId] then
-- target has not previously been abandoned
local previousBuilding = builders[builderId].previousBuilding
if not previousBuilding then
doFastForwardDecision(builderId, targetId, cmdQueue[1].tag, cmdQueue[2].tag)
else
local _, _, _, _, prevBuild = GetUnitHealth(previousBuilding)
if prevBuild == nil or prevBuild == 1 then
-- previous building is gone/done
doFastForwardDecision(builderId, targetId, cmdQueue[1].tag, cmdQueue[2].tag)
end
end
end
end
end
-- prepare outside command queue heuristical candidates/targets
local mpx, _, mpz = GetUnitPosition(builderId, true)
local neighbours = GetUnitsInCylinder(mpx, mpz, builderDef.buildDistance, myTeamId)
-- local candidateNeighboursExclusive = {}
local candidateNeighbours = {}
for i, candidateId in ipairs(neighbours) do
local _, _, _, _, candidateBuild = GetUnitHealth(candidateId)
if candidateBuild ~= nil and candidateBuild < 1 then
table.insert(candidateNeighbours, candidateId)
-- if candidateId ~= builderId then
-- table.insert(candidateNeighboursExclusive, candidateId)
-- end
end
end
-- refresh for possible target change
targetId = GetUnitIsBuilding(builderId)
targetDefID = GetUnitDefID(targetId)
targetDef = UnitDefs[targetDefID]
-- mm/e switcher
local targetUnitMM, targetUnitE = getUnitResourceProperties(targetDefID, targetDef)
-- log('targetUnitE > 0, positiveMMLevel, regpose or eleak === ' .. tostring(targetUnitE > 0) .. ', ' .. tostring(positiveMMLevel) .. ', ' .. tostring((regularizedPositiveEnergy or isEnergyLeaking) ))
-- if targetUnitE > 0 then
-- log(isEnergyLeaking)
-- log('positiveMMLevel, not regnege or eleak === ' .. tostring(positiveMMLevel) .. ', ' .. tostring((regularizedPositiveEnergy or isEnergyLeaking) ))
-- end
if n % (mainIterationModuloLimit * 3) == 0 and #candidateNeighbours > 1 then
if (metalLevel > 0.8 or regularizedPositiveMetal) and (positiveMMLevel or not regularizedNegativeEnergy) then
-- log(builderDef.humanName .. ' ForceAssist buildPower target ' .. targetId .. ' ' .. targetDef.humanName)
builderForceAssist('buildPower', builderId, targetId, targetDefID, candidateNeighbours)
elseif not regularizedPositiveEnergy and not isEnergyLeaking and ((targetUnitMM >= 0 and not positiveMMLevel) or (targetUnitE < 0 and isEnergyStalling)) then
-- log(builderDef.humanName .. ' ForceAssist energy target ' .. targetId .. ' ' .. targetDef.humanName)
builderForceAssist('energy', builderId, targetId, targetDefID, candidateNeighbours)
elseif targetUnitE > 0 and positiveMMLevel and (not regularizedNegativeEnergy or isEnergyLeaking or isMetalStalling) then
-- log(builderDef.humanName .. ' ForceAssist mm target ' .. targetId .. ' ' .. targetDef.humanName)
builderForceAssist('mm', builderId, targetId, targetDefID, candidateNeighbours)
end
end
-- refresh for possible target change
targetId = GetUnitIsBuilding(builderId)
targetDefID = GetUnitDefID(targetId)
targetDef = UnitDefs[targetDefID]
-- easy finish neighbour
local _, _, _, _, targetBuild = GetUnitHealth(targetId)
for _, candidateId in ipairs(candidateNeighbours) do
local candidateDef = unitDef(candidateId)
-- same type and not actually same buildings
if candidateId ~= targetId and candidateDef == targetDef then
local _, _, _, _, candidateBuild = GetUnitHealth(candidateId)
if candidateBuild and candidateBuild < 1 and candidateBuild > targetBuild then
local targetBuildTimeLeft = getBuildTimeLeft(targetId)
if candidateBuild > targetBuild then
repair(builderId, candidateId)
break
end
end
end
end
end
end
end
function purgeCompleteRepairs(builderId, cmdQueue)
local cmdq = deepcopy(cmdQueue)
local shitFound = true
while shitFound do
shitFound = false
for _, cmd in ipairs(cmdQueue) do
if cmd.id == 40 then
local _, _, _, _, targetBuild = GetUnitHealth(cmd.params[1])
if not targetBuild or targetBuild == 1 then
shitFound = true
GiveOrderToUnit(builderId, CMD.REMOVE, {cmd.tag}, {"ctrl"})
end
end
end
cmdq = GetUnitCommands(builderId, 3)
break
end
return cmdq
end
function builderForceAssist(assistType, builderId, targetId, targetDefID, neighbours)
local bestCandidate = getBestCandidate(neighbours, assistType)
if bestCandidate ~= false and targetDefID ~= bestCandidate[2] then
-- log('repair bestCandidate '.. bestCandidate .. ' '.. UnitDefs[GetUnitDefID(bestCandidate)].humanName)
repair(builderId, bestCandidate[1])
end
end
function repair(builderId, targetId)
GiveOrderToUnit(builderId, CMD.INSERT, {0, CMD.REPAIR, CMD.OPT_CTRL, targetId}, {"alt"})
end
function sortHeuristicallyBuildPower(a, b)
local aWillStall = buildingWillStall(a[1])
local bWillStall = buildingWillStall(b[1])
if aWillStall and bWillStall then
return a[3]['power'] < b[3]['power']
elseif aWillStall and not bWillStall then
return false
elseif not aWillStall and bWillStall then
return true
else
return a[3].buildSpeed * (1 / getBuildTimeLeft(a[1])) * (1 / a[3]['power']) > b[3].buildSpeed * (1 / getBuildTimeLeft(b[1])) * (1 / b[3]['power'])
end
end
function sortHeuristicallyEnergy(a, b)
local aWillStall = buildingWillStall(a[1])
local bWillStall = buildingWillStall(b[1])
if aWillStall and bWillStall then
return a[3]['energyMake'] * (1 / getBuildTimeLeft(a[1]) * a[3]['power']) > b[3]['energyMake'] * (1 / getBuildTimeLeft(b[1]) * b[3]['power'])
elseif aWillStall and not bWillStall then
return false
elseif not aWillStall and bWillStall then
return true
else
return a[3]['energyMake'] * a[3]['power'] > b[3]['energyMake'] * b[3]['power']
end
end
function sortHeuristicallyMM(a,b)
-- log('compare ' .. a[3].humanName .. ' '.. getMetalMakingEfficiency(a[2]))
return getMetalMakingEfficiency(a[2]) > getMetalMakingEfficiency(b[2])
end
function getBestCandidate(candidatesOriginal, assistType)
if #candidatesOriginal == 0 then
return false
end
local candidatesFull = deepcopy(candidatesOriginal)
local candidates = {}
for i, candidateId in ipairs(candidatesFull) do
local cdefid = GetUnitDefID(candidateId)
local udef = UnitDefs[cdefid]
local mm_eff = getMetalMakingEfficiency(cdefid)
-- if assistType == 'mm' and mm_eff and mm_eff <= 0 then
-- log('mm eff ' .. getMetalMakingEfficiency(cdefid))
-- end
if
-- udef and (assistType == 'mm' and mm_eff) and
not (assistType == 'buildPower' and (udef.buildSpeed <= 0 or not udef.buildSpeed) ) and
not (assistType == 'energy' and (udef['energyMake'] <= 0 or not udef['energyMake'])) and
not (assistType == 'mm' and mm_eff and mm_eff <= 0) then
candidates[i] = {candidateId, cdefid, udef}
else
-- if assistType == 'mm' then
-- log('removed from candidates ' .. assistType .. ' ' .. udef.humanName)
-- end
end
end
if not candidates[1] or not candidates[2] then
return false
end
if #candidates == 1 then
return candidates[1]
-- todo investigate why number
elseif type(candidates[1]) == "number" or type(candidates[2]) == "number" then
return false
end
if assistType == 'buildPower' then
table.sort(candidates, sortHeuristicallyBuildPower)
elseif assistType == 'energy' then
table.sort(candidates, sortHeuristicallyEnergy)
elseif assistType == 'mm' then
-- log(table.tostring(candidates))
table.sort(candidates, sortHeuristicallyMM)
elseif assistType == 'metal' then
return false
end
-- log(table.tostring(candidates))
-- log(candidates[1][1])
-- return false
return candidates[1]
end
function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function updateFastResourceStatus()
metalMakingLevel = GetTeamRulesParam(myTeamId, 'mmLevel')
local m_curr, m_max, m_pull, m_inc, m_exp = GetTeamResources(myTeamId, 'metal')
local e_curr, e_max, e_pull, e_inc, e_exp = GetTeamResources(myTeamId, 'energy')
isPositiveMetalDerivative = m_inc > (m_pull+m_exp)/2
metalLevel = m_curr/m_max
isPositiveEnergyDerivative = e_inc > (e_pull+e_exp)/2
energyLevel = e_curr/e_max
if energyLevel > metalMakingLevel then
positiveMMLevel = true
else
positiveMMLevel = false
end
isMetalStalling = metalLevel < 0.01 and not regularizedPositiveMetal
isEnergyStalling = energyLevel < 0.01 and not regularizedPositiveEnergy
isMetalLeaking = metalLevel > 0.99 and regularizedPositiveMetal
isEnergyLeaking = energyLevel > 0.99 and isPositiveEnergyDerivative
end
function getUnitResourceProperties(unitDefID, unitDef)
local metalMakingEfficiency = getMetalMakingEfficiency(unitDefID)
if metalMakingEfficiency == nil then
metalMakingEfficiency = 0
end
local energyMaking = getEout(unitDef)
return metalMakingEfficiency, energyMaking
end
function getMetalMakingEfficiency(unitDefID)
local makerDef = WG.energyConversion.convertCapacities[unitDefID]
if makerDef ~= nil then
return makerDef.e
else
return 0
end
end
function getEout(unitDef)
local totalEOut = unitDef.energyMake or 0
totalEOut = totalEOut + -1*unitDef.energyUpkeep
if unitDef.tidalGenerator > 0 and tidalStrength > 0 then
local mult = 1 -- DEFAULT
if unitDef.customParams then
mult = unitDef.customParams.energymultiplier or mult
end
totalEOut = totalEOut +(tidalStrength * mult)
end
if unitDef.windGenerator > 0 then
local mult = 1 -- DEFAULT
if unitDef.customParams then
mult = unitDef.customParams.energymultiplier or mult
end
local unitWindMin = math.min(windMin, unitDef.windGenerator)
local unitWindMax = math.min(windMax, unitDef.windGenerator)
totalEOut = totalEOut + (((unitWindMin + unitWindMax) / 2 ) * mult)
end
return totalEOut
end
-- todo
function getTraveltime(unitDef, A, B)
selectedUnits = GetSelectedUnits()
local totalBuildSpeed = getBuildersBuildSpeed(getUnitsBuildingUnit(targetId))
local secondsLeft = getBuildTimeLeft(targetId)
local unitDef = UnitDefs[GetUnitDefID(targetId)]
if isTimeToMoveOn(secondsLeft, builderId, unitDef, totalBuildSpeed) and not buildingWillStall(secondsLeft, unitDef, totalBuildSpeed) then
moveOnFromBuilding(builderId, targetId, cmdQueueTag, cmdQueueTagg)
end
end
function doFastForwardDecision(builderId, targetId, cmdQueueTag, cmdQueueTagg)
selectedUnits = GetSelectedUnits()
local totalBuildSpeed = getBuildersBuildSpeed(getUnitsBuildingUnit(targetId))
local secondsLeft = getBuildTimeLeft(targetId)
local unitDef = UnitDefs[GetUnitDefID(targetId)]
if isTimeToMoveOn(secondsLeft, builderId, unitDef, totalBuildSpeed) and not buildingWillStall(targetId) then
moveOnFromBuilding(builderId, targetId, cmdQueueTag, cmdQueueTagg)
end
end
function moveOnFromBuilding(builderId, targetId, cmdQueueTag, cmdQueueTagg)
if not cmdQueueTagg then
GiveOrderToUnit(builderId, CMD.REMOVE, {cmdQueueTag}, {"ctrl"})
else
GiveOrderToUnit(builderId, CMD.REMOVE, {cmdQueueTag,cmdQueueTagg}, {"ctrl"})
end
builders[builderId].previousBuilding = targetId
abandonedTargetIDs[targetId] = true
t1 = Spring.GetTimer()
end
function isTimeToMoveOn(secondsLeft, builderId, unitDef, totalBuildSpeed)
local plannerBuildSpeed = builders[builderId].originalBuildSpeed
local plannerBuildShare = plannerBuildSpeed / totalBuildSpeed
local slowness = 45/unitDef.speed
if ((plannerBuildShare < 0.75 and secondsLeft < 1.2*slowness) or (plannerBuildShare < 0.5 and secondsLeft < 3.4*slowness) or (plannerBuildShare < 0.15 and secondsLeft < 8*slowness) or (plannerBuildShare < 0.05 and secondsLeft < 12*slowness)) then
totalSavedTime = totalSavedTime + secondsLeft
return true
else
return false
end
end
function buildingWillStall(unitId)
local secondsLeft = getBuildTimeLeft(unitId)
local unitDef = unitDef(unitId)
local speed = unitDef.buildTime / getBuildersBuildSpeed(getUnitsBuildingUnit(unitId))
local metal = unitDef.metalCost / speed
local energy = unitDef.energyCost / speed
local mDrain, eDrain = getUnitsUpkeep()
if buildingWillStallType("metal", metal, secondsLeft, mDrain) or buildingWillStallType("energy", energy, secondsLeft, eDrain) then
return true
else
return false
end
end
function buildingWillStallType(type, consumption, secondsLeft, releasedExpenditures)
local currentChange, lvl, storage, _, alreadyInStall = getMyResources(type)
local changeWhenBuilding = currentChange - consumption + releasedExpenditures
if metalMakersControlled and type == "metal" then
changeWhenBuilding = changeWhenBuilding - releasedMetal
end
releasedMetal = 0
if metalMakersControlled and type == "energy" and possibleMetalMakersUpkeep > 0 then
local metalMakersUpkeep = getMetalMakersUpkeep()
if changeWhenBuilding < 0 then
changeWhenBuilding = changeWhenBuilding + metalMakersUpkeep
local releasedEnergy = 0
if changeWhenBuilding > 0 then
releasedEnergy = changeWhenBuilding
changeWhenBuilding = 0
else
releasedEnergy = metalMakersUpkeep
end
releasedMetal = possibleMetalMakersProduction * releasedEnergy / possibleMetalMakersUpkeep
end
end
local after = lvl + secondsLeft * changeWhenBuilding
if consumption < 1 or (not alreadyInStall and after > 0) then
return false
else
return true
end
end
function getMyResources(type)
local lvl, storage, pull, inc, exp, share, sent , recieved = GetTeamResources(myTeamId, type)
if not inc then
log("ERROR", myTeamId, type)
myTeamId = Spring.GetMyTeamID()
return
end
local total = recieved
local exp = 0
local units = GetTeamUnits(myTeamId)
if type == "metal" then
for _, unitID in ipairs(units) do
local metalMake, metalUse, energyMake, energyUse = GetUnitResources(unitID)
total = total + metalMake - metalUse
exp = exp + metalUse
end
else
for _, unitID in ipairs(units) do
local metalMake, metalUse, energyMake, energyUse = GetUnitResources(unitID)
total = total + energyMake - energyUse
exp = exp + energyUse
end
end
local alreadyInStall = pull > exp and lvl < pull
return total, lvl, storage, exp, alreadyInStall
end
function getUnitsUpkeep()
local alreadyCounted = {}
local metal = 0
local energy = 0
for _, unitId in ipairs(GetTeamUnits(myTeamId)) do
local unitDef = unitDef(unitId)
if unitDef.canAssist then
local metalUse, energyUse = traceUpkeep(unitId, alreadyCounted)
metal = metal + metalUse
energy = energy + energyUse
end
end
return metal, energy
end
function unitDef(unitId)
return UnitDefs[GetUnitDefID(unitId)]
end
function getSelectedUnitsUpkeep()
local alreadyCounted = {}
local metal = 0
local energy = 0
for _, unitID in ipairs(selectedUnits) do
if builders[unitID] then
local metalUse, energyUse = traceUpkeep(unitID, alreadyCounted)
metal = metal + metalUse
energy = energy + energyUse
end
end
return {["metal"] = metal, ["energy"] = energy}
end
function traceUpkeep(unitID, alreadyCounted)
if alreadyCounted[unitID] then
return 0, 0
end
local metalMake, metal, energyMake, energy = GetUnitResources(unitID)
for _, guardID in ipairs(builders[unitID].guards) do
if builders[guardID].owned then
local guarderMetal, guarderEnergy = traceUpkeep(guardID, alreadyCounted)
metal = metal + guarderMetal
energy = energy + guarderEnergy
end
end
alreadyCounted[unitID] = unitID
return metal - metalMake + builders[unitID].unitDef.metalMake, energy - energyMake + builders[unitID].unitDef.energyMake
end
-- for debug
function log(s)
Spring.Echo(s)
end
function table.has_value(tab, val)
for _, value in ipairs (tab) do
if value == val then
return true
end
end
return false
end
function table.full_of(tab, val)
for _, value in ipairs (tab) do
if value ~= val then
return false
end
end
return true
end
-- for printing tables
function table.val_to_str(v)
if "string" == type(v) then
v = string.gsub(v, "\n", "\\n" )
if string.match(string.gsub(v,"[^'\"]",""), '^"+$' ) then
return "'" .. v .. "'"
end
return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
else
return "table" == type(v) and table.tostring(v) or
tostring(v)
end
end
function table.key_to_str(k)
if "string" == type(k) and string.match(k, "^[_%a][_%a%d]*$" ) then
return k
else
return "[" .. table.val_to_str(k) .. "]"
end
end
function table.tostring(tbl)
local result, done = {}, {}
for k, v in ipairs(tbl ) do
table.insert(result, table.val_to_str(v) )
done[ k ] = true
end
for k, v in pairs(tbl) do
if not done[ k ] then
table.insert(result,
table.key_to_str(k) .. "=" .. table.val_to_str(v) )
end
end
return "{" .. table.concat(result, "," ) .. "}"
end