· 5 years ago · Jul 30, 2020, 12:26 PM
1-- For debugging purposes
2--
3function table.val_to_str ( v )
4 if "string" == type( v ) then
5 v = string.gsub( v, "\n", "\\n" )
6 if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
7 return "'" .. v .. "'"
8 end
9 return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
10 else
11 return "table" == type( v ) and table.tostring( v ) or
12 tostring( v )
13 end
14end
15
16function table.key_to_str ( k )
17 if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
18 return k
19 else
20 return "[" .. table.val_to_str( k ) .. "]"
21 end
22end
23
24function table.tostring( tbl )
25 local result, done = {}, {}
26 for k, v in ipairs( tbl ) do
27 table.insert( result, table.val_to_str( v ) )
28 done[ k ] = true
29 end
30 for k, v in pairs( tbl ) do
31 if not done[ k ] then
32 table.insert( result,
33 table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
34 end
35 end
36 return "{" .. table.concat( result, "," ) .. "}"
37end
38--]]
39
40local tickRate = 20
41local regenDelay
42if settings.global["stormwalls-wall-regen"].value then regenDelay = 1 else regenDelay = tickRate / 2 end
43local fieldSuffix = "-stormwall"
44local fieldGateSuffix = "-stormwall-gate"
45local emitterName = "stormwall-emitter"
46local emitterRange = 33.5
47
48--[[ Storage structure for the mod's data
49global.
50 "activeEmitters". - list of active emitters
51
52 "plates". - list of forcefield plate tiles
53 ... [surface]
54 ... [x]
55 ... [y]
56 {colour, linked emitter unit_number, field unit_number, orientation} -- [4] is only used for gates, otherwise 'false'
57
58 "fields". - list of forcefield entities
59 ... -- unit_number
60 {colour, linked emitter unit_number, field entity, surface, x, y}
61
62 "degradingFields" - list of forcefields to be degraded
63 ... -- unit_number
64 {field colour, field entity, surface, x, y}
65
66 "emitters". - list of emitter entities
67 ... -- unit_number for key
68 "entity" -- entity reference
69 "surface" -- index for the surface this emitter is on
70 "active" -- is the emitter active?
71 "linked_plates" -- list of field plate coordinates
72 ... [x]
73 ... [y]
74 {colour}
75 "generating_fields" -- list of spawn times and fields to be spawned on respective tick
76 ... - tick
77 {colour, x, y, orientation}
78 "damaged_fields" -- list of damaged fields to be repaired
79 ... - unit_number
80 {colour, entity, next tick to start repairing}
81 "destroyed"
82 "disabled" -- has the emitter been marked for deconstruction
83
84 "ticking" - boolean; should tick() be running?
85]]--
86
87--[[
88 chargeRate: tickRate * chargeRate = new field health generation per tick
89 degradeRate: tickRate * degradeRate = health loss per tick when degrading
90 respawnRate: tickRate * respawnRate = ticks between field respawn
91 energyPerCharge: tickRate * energyPerCharge = energy used per initial health generation
92 energyPerRespawn: tickRate * energyPerRespawn = energy per field respawn
93 energyPerHealthLost: the amount of energy per health lost to regenerate the field once the field is fully generated
94]]--
95
96local stormwallTypes =
97{
98 ["blue"] =
99 {
100 chargeRate = 0.2036111111111111,
101 degradeRate = 2.777777777777778,
102 respawnRate = 15,
103 -- energyPerCharge = 4200,
104 energyPerRespawn = 5000,
105 energyPerHealthLost = 17000,
106 maxHealth = 300
107 },
108 ["green"] =
109 {
110 chargeRate = 0.175,
111 degradeRate = 2,
112 respawnRate = 50,
113 -- energyPerCharge = 4000,
114 energyPerRespawn = 20000,
115 energyPerHealthLost = 16000,
116 maxHealth = 700
117 },
118 ["purple"] =
119 {
120 chargeRate = 0.2083333333333334,
121 degradeRate = 3.333333333333333,
122 respawnRate = 100,
123 -- energyPerCharge = 7000,
124 energyPerRespawn = 10000,
125 energyPerHealthLost = 25000,
126 deathEntity = "stormwall-death-damage",
127 maxHealth = 150
128 },
129 ["red"] =
130 {
131 chargeRate = 0.175,
132 degradeRate = 4.333333333333333,
133 respawnRate = 30,
134 -- energyPerCharge = 10000,
135 energyPerRespawn = 50000,
136 energyPerHealthLost = 40000,
137 maxHealth = 300
138 }
139}
140
141remote.add_interface("stormwalls", {
142 test = function()
143 for _,player in pairs(game.players) do
144 player.get_inventory(defines.inventory.player_guns).clear()
145 player.get_inventory(defines.inventory.player_ammo).clear()
146 player.get_inventory(defines.inventory.player_quickbar).clear()
147 player.clear_items_inside()
148 local inventory_armor = player.get_inventory(defines.inventory.player_armor)
149 inventory_armor.clear()
150
151 player.insert({name = emitterName, count=10})
152 for stormwall,_ in pairs(stormwallTypes) do
153 player.insert({name = stormwall .. "-stormwall-plate", count=50})
154 player.insert({name = stormwall .. "-stormwall-plate-gate", count=50})
155 end
156 player.insert({name = "solar-panel", count=176})
157 player.insert({name = "medium-electric-pole", count = 37})
158 player.insert({name = "accumulator", count = 148})
159 player.insert({name = "construction-robot", count = 50})
160 player.insert({name = "submachine-gun"})
161 player.insert({name = "piercing-rounds-magazine", count = 50})
162 player.insert({name = "concrete", count = 50})
163 player.insert({name = "rail", count = 50})
164
165 inventory_armor.insert({name = "power-armor-mk2"})
166 local armorgrid = inventory_armor[1].grid
167 equipmentList = {
168 ["fusion-reactor-equipment"] = {{0,0},{6,0}},
169 ["night-vision-equipment"] = {{4,0}},
170 ["battery-mk2-equipment"] = {{4,2},{5,2}},
171 ["personal-laser-defense-equipment"] = {{0,4},{2,4},{4,4},{6,4},{8,4}},
172 ["exoskeleton-equipment"] = {{0,6},{2,6}},
173 ["personal-roboport-mk2-equipment"] = {{4,6},{4,8}},
174 ["energy-shield-mk2-equipment"] = {{6,6},{6,8},{8,6},{8,8}}
175 }
176 for i,j in pairs(equipmentList) do
177 for k,l in pairs(j) do
178 armorgrid.put{name = i, position = l}
179 end
180 end
181 end
182 end,
183
184 check = function()
185 game.print("Current tick: " .. game.tick, {r = 0.1, g = 0.1, b = 0.8})
186 game.print("Global plate list: " .. table.tostring(global.plates))
187 game.print("Global field list: " .. table.tostring(global.fields))
188 game.print("Global degrading field list: " .. table.tostring(global.degradingFields))
189 -- game.print(tostring(script.get_event_handler(defines.events.on_tick)))
190 -- game.print("Ticking: Yes")
191 -- else
192 -- game.print("Ticking: No")
193 -- end
194 for i,j in pairs(global.emitters) do
195 local active
196 if global.emitters[i]["active"] == true then active = "(Active)" else active = "(Idle)" end
197 game.print("Emitter " .. i .. active .. ": " .. table.tostring(global.emitters[i]["linked_plates"]))
198 game.print("Generating fields: " .. table.tostring(global.emitters[i]["generating_fields"]))
199 game.print("Damaged fields: " .. table.tostring(global.emitters[i]["damaged_fields"]))
200 end
201
202 -- game.print(defines.direction.north .. defines.direction.east .. defines.direction.south .. defines.direction.west)
203 end
204})
205
206function throwError(what)
207 game.print(what)
208end
209
210script.on_init(function(event)
211 global.plates = {}
212 global.fields = {}
213 global.emitters = {}
214 global.activeEmitters = {}
215 global.degradingFields = {}
216end)
217
218script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
219 if event.setting == "stormwalls-wall-regen" then regenConfig() end
220end)
221
222function regenConfig()
223 if settings.global["stormwalls-wall-regen"].value then
224 regenDelay = 1
225 else
226 regenDelay = tickRate / 2
227 end
228end
229
230script.on_load(function(event)
231 if global.ticking then
232 script.on_nth_tick(tickRate, tick)
233 end
234end)
235
236function tileBuilt(event)
237 local plateColour, orientation = string.match(event.item.name, '^(%a+)%-stormwall%-plate([%a-]*)$')
238 local surfaceIndex
239 local fieldIndex, fieldEntity, fieldColour
240
241 if event.surface_index ~= nil then -- lets play find the surface index
242 surfaceIndex = event.surface_index
243 else
244 surfaceIndex = event.robot.surface.index
245 end
246
247 if plateColour ~= nil then
248 if global.plates[surfaceIndex] == nil then
249 global.plates[surfaceIndex] = {}
250 end
251 if orientation ~= "" then
252 if game.surfaces[surfaceIndex].count_tiles_filtered{area = {{event.tiles[1].position.x, event.tiles[1].position.y},{event.tiles[1].position.x + 1, event.tiles[1].position.y + 1}}, name = plateColour .. "-stormwall-plate-gate-horizontal"} == 1 then
253 orientation = "h"
254 else
255 orientation = "v"
256 end
257 else
258 orientation = false
259 end
260
261 for _,k in pairs(event.tiles) do
262 oldPlateColour = string.match(k.old_tile.name, '^(%a+)%-stormwall%-plate[%a-]*$')
263
264 if oldPlateColour ~= nil then
265 fieldIndex, fieldEntity, fieldColour = loadField(surfaceIndex, k.position.x, k.position.y) -- find connected field, if any
266 if fieldIndex ~= 0 and global.degradingFields[fieldIndex] == nil then
267 setFieldDegrading(fieldIndex, fieldColour, fieldEntity, surfaceIndex, k.position.x, k.position.y) -- degrade non-matching field
268 activateTicker()
269 end
270 else
271 fieldIndex, fieldEntity, fieldColour = 0, 0, 0
272 end
273
274 if global.plates[surfaceIndex][k.position.x] == nil then
275 global.plates[surfaceIndex][k.position.x] = {}
276 end
277
278 local emitterEntity, emitterIndex = scanForEmitters(surfaceIndex, k.position.x, k.position.y, 0) -- find connectable emitter, if any
279
280 if emitterEntity ~= 0 then
281 linkPlateToEmitter(surfaceIndex, k.position.x, k.position.y, plateColour, emitterIndex, fieldIndex, orientation) -- link new plate to emitter or modify existing plate data
282 setFieldBuilding(plateColour, k.position.x, k.position.y, global.emitters[emitterIndex], orientation)
283 else
284 global.plates[surfaceIndex][k.position.x][k.position.y] = {plateColour, 0, 0, orientation} -- no emitter to be found
285 end
286 end
287
288 -- remote.call("stormwalls", "check")
289 else
290 for _,k in pairs(event.tiles) do
291 oldPlateColour = string.match(k.old_tile.name, '^(%a+)%-stormwall%-plate[%a-]*$')
292
293 if oldPlateColour ~= nil then
294 fieldIndex, fieldEntity, fieldColour = loadField(surfaceIndex, k.position.x, k.position.y) -- find connected field, if any
295 if fieldIndex ~= 0 and global.degradingFields[fieldIndex] == nil then
296 setFieldDegrading(fieldIndex, fieldColour, fieldEntity, surfaceIndex, k.position.x, k.position.y) -- degrade non-matching field
297 activateTicker()
298 end
299
300 global.plates[surfaceIndex][k.position.x][k.position.y] = nil
301 if next(global.plates[surfaceIndex][k.position.x]) == nil then
302 global.plates[surfaceIndex][k.position.x] = {}
303 end
304 end
305 end
306 end
307end
308
309script.on_event(defines.events.on_player_built_tile, tileBuilt)
310script.on_event(defines.events.on_robot_built_tile, tileBuilt)
311
312function tileMined(event)
313 for i,j in pairs(event.tiles) do
314 local plateColour = string.match(j.old_tile.name, '^(%a+)%-stormwall%-plate[%a-]*$')
315
316 if plateColour ~= nil then
317 local surfaceIndex
318 if event.surface_index ~= nil then
319 surfaceIndex = event.surface_index
320 else
321 surfaceIndex = event.robot.surface.index
322 end
323 local plate = global.plates[surfaceIndex][j.position.x][j.position.y]
324
325 if plate[2] ~= 0 then
326 local emitterTable = global.emitters[plate[2]]
327 local fieldIndex, fieldEntity, fieldColour = loadField(surfaceIndex, j.position.x, j.position.y)
328 emitterTable["linked_plates"][j.position.x][j.position.y] = nil -- remove linked plate
329
330 if next(emitterTable["linked_plates"][j.position.x]) == nil then
331 emitterTable["linked_plates"][j.position.x] = nil
332 end
333
334 if fieldEntity ~= 0 then -- set linked forcefield, if any, to degrade
335 setFieldDegrading(fieldEntity.unit_number, fieldColour, fieldEntity, emitterTable["surface"], j.position.x, j.position.y)
336 end
337 end
338
339 global.plates[surfaceIndex][j.position.x][j.position.y] = nil
340
341 if next(global.plates[surfaceIndex][j.position.x]) == nil then
342 global.plates[surfaceIndex][j.position.x] = nil
343
344 if next(global.plates[surfaceIndex]) == nil then
345 global.plates[surfaceIndex] = nil
346 end
347 end
348
349 -- remote.call("stormwalls", "check")
350 end
351 end
352end
353
354script.on_event(defines.events.on_player_mined_tile, tileMined)
355script.on_event(defines.events.on_robot_mined_tile, tileMined)
356
357function entityBuilt(event)
358 if event.created_entity.name == emitterName then
359 local emitterEntity = event.created_entity
360 local newEmitter = {}
361
362 newEmitter["entity"] = emitterEntity
363 newEmitter["surface"] = emitterEntity.surface.index
364 newEmitter["unit_number"] = emitterEntity.unit_number
365 newEmitter["linked_plates"] = {} -- list of plates connected to this emitter
366 newEmitter["generating_fields"] = {} -- fields to be constructed
367 newEmitter["damaged_fields"] = {} -- fields that are being iterated over for repairing
368 newEmitter["active"] = false
369 newEmitter["destroyed"] = false -- emitter has been destroyed
370 newEmitter["disabled"] = false -- emitter has been marked for deconstruction
371
372 global.emitters[newEmitter["unit_number"]] = newEmitter
373
374 local availablePlates, availablePlateColours, availablePlateOrientations = scanForPlates(newEmitter["surface"], emitterEntity.position.x, emitterEntity.position.y, newEmitter["unit_number"])
375 local fieldIndex, fieldEntity, fieldColour
376
377 for i,j in next,availablePlates,nil do
378 fieldIndex, fieldEntity, fieldColour = loadField(newEmitter["surface"], j.position.x, j.position.y)
379 linkPlateToEmitter(newEmitter["surface"], j.position.x, j.position.y, availablePlateColours[i], newEmitter["unit_number"], fieldIndex, availablePlateOrientations[i])
380 if fieldIndex == 0 then
381 setFieldBuilding(availablePlateColours[i], j.position.x, j.position.y, newEmitter, availablePlateOrientations[i])
382 else
383 linkFieldToEmitter(fieldIndex, fieldColour, newEmitter["unit_number"], fieldEntity, newEmitter["surface"], j.position.x, j.position.y)
384 end
385 end
386
387 -- remote.call("stormwalls", "check")
388 end
389end
390
391script.on_event(defines.events.on_built_entity, entityBuilt)
392script.on_event(defines.events.on_robot_built_entity, entityBuilt)
393
394function entityRemoved(event)
395 if string.match(event.entity.name, '^%a+%-stormwall[%a-]*$') ~= nil then
396 local fieldIndex = event.entity.unit_number
397 local fieldColour = global.fields[fieldIndex][1]
398 local x = global.fields[fieldIndex][5]
399 local y = global.fields[fieldIndex][6]
400 local emitterIndex = global.fields[fieldIndex][2]
401 local surfaceIndex = event.entity.surface.index
402
403 if stormwallTypes[fieldColour]["deathEntity"] ~= nil then
404 event.entity.surface.create_entity({name = stormwallTypes[fieldColour]["deathEntity"], position = {x, y}, force = event.entity.force})
405 end
406
407 global.fields[fieldIndex] = nil
408 if global.plates[surfaceIndex][x] ~= nil and global.plates[surfaceIndex][x][y] ~= nil then
409 global.plates[event.entity.surface.index][x][y][3] = 0
410 end
411 if global.emitters[emitterIndex] ~= nil then -- no rebuilds for dying fields with no linked emitter in range
412 setFieldBuilding(fieldColour, x, y, global.emitters[emitterIndex], global.plates[event.entity.surface.index][x][y][4])
413
414 if global.emitters[emitterIndex]["damaged_fields"][fieldIndex] ~= nil then
415 global.emitters[emitterIndex]["damaged_fields"][fieldIndex] = nil -- no repairs for dead fields
416 end
417 else
418 global.degradingFields[fieldIndex] = nil -- no degrading for fields dying ahead of schedule
419 end
420 elseif event.entity.name == emitterName then
421 local emitterTable = global.emitters[event.entity.unit_number]
422
423 for i,j in pairs(emitterTable["linked_plates"]) do
424 for k,_ in pairs(j) do
425 local emitterEntity, emitterIndex = scanForEmitters(emitterTable["surface"], i, k, emitterTable["unit_number"])
426 local surfaceIndex = emitterTable["surface"]
427 local plateColour = emitterTable["linked_plates"][i][k]
428 local plateOrientation = global.plates[surfaceIndex][i][k][4]
429 local fieldIndex, fieldEntity, fieldColour = loadField(surfaceIndex, i, k) -- find connected field, if any
430
431 if emitterEntity ~= 0 then
432 linkPlateToEmitter(surfaceIndex, i, k, plateColour, emitterIndex, fieldIndex, plateOrientation) -- link plate to emitter
433
434 if fieldEntity ~= 0 then
435 linkFieldToEmitter(fieldIndex, fieldColour, emitterIndex, fieldEntity, surfaceIndex, i, k) -- link field to emitter
436 else
437 setFieldBuilding(plateColour, i, k, global.emitters[emitterIndex], global.plates[surfaceIndex][i][k][4]) -- otherwise start a new field building
438 end
439 else
440 global.plates[surfaceIndex][i][k][2] = 0 -- unlink emitter from plate, make it available to be linked to another emitter
441
442 if fieldEntity ~= 0 then
443 global.plates[surfaceIndex][i][k][3] = 0 -- unlink field from plate, and kill it
444 setFieldDegrading(fieldIndex, fieldColour, fieldEntity, surfaceIndex, i, k)
445 end
446 end
447
448
449 end
450 end
451
452 global.emitters[emitterTable["unit_number"]] = nil
453 end
454
455 -- remote.call("stormwalls", "check")
456end
457
458script.on_event(defines.events.on_player_mined_entity, entityRemoved)
459script.on_event(defines.events.on_robot_mined_entity, entityRemoved)
460script.on_event(defines.events.on_entity_died, entityRemoved)
461
462function entityDamaged(event)
463 local fieldColour = string.match(event.entity.name, '^(%a+)%-stormwall[%a-]*$')
464
465 if fieldColour ~= nil then
466 local fieldEntity = event.entity
467 local fieldIndex = event.entity.unit_number
468 local emitterIndex = global.fields[fieldIndex][2]
469
470 if global.fields[fieldIndex][2] ~= 0 then
471 global.emitters[emitterIndex]["damaged_fields"][fieldIndex] = {fieldColour, fieldEntity, game.tick + regenDelay * stormwallTypes[fieldColour]["respawnRate"]}
472 setActive(emitterIndex)
473 end
474 end
475end
476
477script.on_event(defines.events.on_entity_damaged, entityDamaged)
478
479function setActive(emitterIndex) -- activate an emitter and set the ticker active to iterate over it
480 local emitterTable = global.emitters[emitterIndex]
481 if emitterTable["disabled"] == true then
482 return
483 end
484
485 if emitterTable["active"] == false then
486 emitterTable["active"] = true
487 table.insert(global.activeEmitters, emitterTable)
488 activateTicker()
489 -- throwError("Active emitter:" .. emitterTable["unit_number"])
490 end
491end
492
493function activateTicker() -- turns on the ticker
494 if not global.ticking then
495 global.ticking = true
496 script.on_nth_tick(tickRate, tick)
497 end
498end
499
500function scanForPlates(surfaceIndex, x, y, emitterIndex) -- find unlinked plates in range of a position
501 local emitterEntity = global.emitters[emitterIndex]["entity"]
502 local plateList = {}
503 local plateListColours = {}
504 local plateListOrientations = {}
505
506 for i,j in pairs(stormwallTypes) do
507 for _,k in pairs({"", "-gate-horizontal", "-gate-vertical"}) do
508 local plates = emitterEntity.surface.find_tiles_filtered{area = {{x - emitterRange, y - emitterRange},{x + emitterRange, y + emitterRange}}, name = i .. "-stormwall-plate" .. k}
509 for _,l in pairs(plates) do
510 if global.plates[surfaceIndex][l.position.x][l.position.y][2] == 0 then
511 plateList[#plateList + 1] = l
512 end
513 end
514 end
515 end
516
517 for i,j in next,plateList,nil do
518 plateListColours[i], plateListOrientations[i] = string.match(j.name, '^(%a+)%-stormwall%-plate([%a-]*)$')
519
520 if plateListOrientations[i] == "" then
521 plateListOrientations[i] = false
522 elseif plateListOrientations[i] == "-gate-horizontal" then
523 plateListOrientations[i] = "h"
524 else
525 plateListOrientations[i] = "v"
526 end
527 end
528
529 return plateList, plateListColours, plateListOrientations
530end
531
532function scanForEmitters(surfaceIndex, x, y, emitterIndex) -- find an emitter to link to except emitterIndex
533 local emitterList = game.surfaces[surfaceIndex].find_entities_filtered{area = {{x - emitterRange, y - emitterRange},{x + emitterRange, y + emitterRange}}, name = emitterName, type = "electric-energy-interface"}
534
535 if #emitterList ~= 0 then
536 for i,j in pairs(emitterList) do
537 if j.unit_number ~= emitterIndex then -- global.plates[surfaceIndex][x][y][2]
538 return j, j.unit_number
539 end
540 end
541 end
542
543 return 0, 0
544end
545
546function loadField(surfaceIndex, x, y)
547 local fieldIndex = global.plates[surfaceIndex][x][y][3]
548
549 if fieldIndex ~= 0 and global.fields[fieldIndex] ~= nil then
550 -- throwError(fieldIndex)
551 local fieldEntity = global.fields[fieldIndex][3]
552 local fieldColour = global.fields[fieldIndex][1]
553
554 return fieldIndex, fieldEntity, fieldColour
555 else
556 return 0, 0, 0
557 end
558end
559
560function linkPlateToEmitter(surfaceIndex, x, y, colour, emitterIndex, fieldIndex, orientation)
561 if global.plates[surfaceIndex][x] == nil then
562 global.plates[surfaceIndex][x] = {}
563 end
564 if global.emitters[emitterIndex]["linked_plates"][x] == nil then
565 global.emitters[emitterIndex]["linked_plates"][x] = {}
566 end
567
568 global.plates[surfaceIndex][x][y] = {colour, emitterIndex, fieldIndex, orientation}
569 global.emitters[emitterIndex]["linked_plates"][x][y] = colour -- add to linked emitter's plate list
570 setActive(emitterIndex)
571end
572
573function linkFieldToEmitter(fieldIndex, fieldColour, emitterIndex, fieldEntity, surfaceIndex, x, y) -- create field entry and link to emitter
574 global.fields[fieldIndex] = {fieldColour, emitterIndex, fieldEntity, surfaceIndex, x, y}
575
576 if fieldEntity.health < stormwallTypes[fieldColour]["maxHealth"] then
577 global.emitters[emitterIndex]["damaged_fields"][fieldIndex] = {fieldColour, fieldEntity, game.tick + regenDelay * stormwallTypes[fieldColour]["respawnRate"]}
578 setActive(emitterIndex)
579 end
580
581 if global["degradingFields"][fieldIndex] ~= nil then
582 global["degradingFields"][fieldIndex] = nil
583 end
584end
585
586function setFieldDegrading(fieldIndex, fieldColour, fieldEntity, surfaceIndex, x, y) -- once a field is degrading, it dies, no matter what
587 global.degradingFields[fieldIndex] = {fieldColour, fieldEntity, surfaceIndex, x, y}
588 if global.plates[surfaceIndex] ~= nil and global.plates[surfaceIndex][x] ~= nil and global.plates[surfaceIndex][x][y] ~= nil then -- making sure the plate entry still exists
589 global.plates[surfaceIndex][x][y][3] = 0
590 end
591 if global.fields[fieldIndex][2] ~= 0 and global.emitters[global.fields[fieldIndex][2]]["damaged_fields"][fieldIndex] ~= nil then
592 global.emitters[global.fields[fieldIndex][2]]["damaged_fields"][fieldIndex] = nil -- no repairs for dying fields
593 end
594 global.fields[fieldIndex][2] = 0 -- remove link to emitter so tick() won't attempt to rebuild
595
596 activateTicker()
597end
598
599function setFieldBuilding(fieldColour, x, y, emitterTable, orientation)
600 local triggerTick = game.tick + tickRate * stormwallTypes[fieldColour].respawnRate
601
602 if emitterTable["generating_fields"][triggerTick] == nil then
603 emitterTable["generating_fields"][triggerTick] = {}
604 end
605
606 table.insert(emitterTable["generating_fields"][triggerTick], {fieldColour, x, y, orientation})
607 setActive(emitterTable["unit_number"])
608end
609
610
611function tick()
612 local shouldKeepTicking = false
613
614 for i,j in pairs(global.activeEmitters) do -- check to make sure all emitters are still valid
615 if j["entity"].valid == false then
616 table.remove(global.activeEmitters,i)
617 end
618 end
619
620 -- active emitters
621 if next(global.activeEmitters) ~= nil then
622 shouldKeepTicking = true
623
624 for k,emitterTable in pairs(global.activeEmitters) do
625 local currentEnergy = emitterTable["entity"].energy
626 if currentEnergy >= tickRate * (stormwallTypes["red"]["energyPerRespawn"] + stormwallTypes["red"]["energyPerHealthLost"]) then
627
628 -- check for fields to repair
629 for i,j in pairs(emitterTable["damaged_fields"]) do
630 local healthRegen = tickRate * stormwallTypes[j[1]]["chargeRate"]
631 local energyCost = healthRegen * stormwallTypes[j[1]]["energyPerHealthLost"]
632
633 if game.tick > j[3] and currentEnergy >= energyCost then
634 j[2].health = j[2].health + healthRegen
635 emitterTable["entity"].energy = currentEnergy - energyCost
636
637 if j[2].health >= stormwallTypes[j[1]]["maxHealth"] then
638 emitterTable["damaged_fields"][i] = nil
639 end
640 else
641 break
642 end
643 end
644
645 -- check for fields to build
646 for i,j in pairs(emitterTable["generating_fields"]) do
647 if i < game.tick then
648 for k,l in pairs(j) do
649 local colour = l[1]
650 local energyCost = stormwallTypes[colour]["energyPerRespawn"]
651
652 if currentEnergy >= energyCost
653 and global.plates[emitterTable["surface"]] ~= nil -- this block of ands makes sure the plate hasn't been mined or fast-replaced between initiating build and the actual build tick
654 and global.plates[emitterTable["surface"]][l[2]] ~= nil
655 and global.plates[emitterTable["surface"]][l[2]][l[3]] ~= nil
656 and colour == global.plates[emitterTable["surface"]][l[2]][l[3]][1]
657 and l[4] == global.plates[emitterTable["surface"]][l[2]][l[3]][4] then
658 local nextTick = game.tick + tickRate * stormwallTypes[colour]["respawnRate"]
659 local fieldName
660 local fieldOrientation
661 if global.plates[emitterTable["surface"]][l[2]][l[3]][4] == false then
662 fieldName = colour .. fieldSuffix
663 fieldOrientation = defines.direction.north
664 elseif global.plates[emitterTable["surface"]][l[2]][l[3]][4] == "v" then
665 fieldName = colour .. fieldGateSuffix
666 fieldOrientation = defines.direction.north
667 else
668 fieldName = colour .. fieldGateSuffix
669 fieldOrientation = defines.direction.east
670 end
671
672 if game.surfaces[emitterTable["surface"]].can_place_entity{name = fieldName, position = {l[2], l[3]}, direction = fieldOrientation, force = emitterTable.entity.force} then
673 local createdField
674 if l[4] == false then
675 createdField = game.surfaces[emitterTable["surface"]].create_entity{name = fieldName, position = {l[2], l[3]}, force = emitterTable.entity.force}
676 elseif l[4] == "h" then
677 createdField = game.surfaces[emitterTable["surface"]].create_entity{name = fieldName, position = {l[2], l[3]}, direction = defines.direction.east, force = emitterTable.entity.force}
678 else
679 createdField = game.surfaces[emitterTable["surface"]].create_entity{name = fieldName, position = {l[2], l[3]}, force = emitterTable.entity.force}
680 end
681 createdField.health = 1
682 currentEnergy = currentEnergy - energyCost
683 global.plates[emitterTable["surface"]][l[2]][l[3]][3] = createdField.unit_number
684 global.fields[createdField.unit_number] = {colour, emitterTable["unit_number"], createdField, emitterTable["surface"], l[2], l[3]}
685 emitterTable["damaged_fields"][createdField.unit_number] = {colour, createdField, game.tick} -- pass it over to the repair check
686 else
687 if emitterTable["generating_fields"][nextTick] == nil then
688 emitterTable["generating_fields"][nextTick] = {}
689 end
690 table.insert(emitterTable["generating_fields"][nextTick],l)
691 end
692 end
693 end
694
695 emitterTable["generating_fields"][i] = nil
696 else
697 break -- no more fields to be built for now
698 end
699 end
700 end
701
702 if next(emitterTable["damaged_fields"]) == nil and next(emitterTable["generating_fields"]) == nil then
703 table.remove(global.activeEmitters, k)
704 emitterTable["active"] = false
705 end
706 end
707 end
708
709 -- degrading forcefields
710 if global["degradingFields"] ~= nil then
711 shouldKeepTicking = true
712
713 for i,j in pairs(global["degradingFields"]) do
714 local newHealth = j[2].health - tickRate * stormwallTypes[j[1]]["degradeRate"]
715
716 if newHealth <= 0 then -- degrading field's ready to die
717 j[2].destroy()
718
719 if global.fields[i][2] ~= 0 then -- if the plate was fast-replaced, build the new field
720 setFieldBuilding(j[1], j[4], j[5], global.emitters[global.fields[i][2]])
721 end
722 global.fields[i] = nil
723 global.degradingFields[i] = nil
724 else
725 j[2].health = newHealth
726 end
727 end
728 end
729
730 if not shouldKeepTicking then
731 global.ticking = nil
732 script.on_nth_tick(nil)
733 end
734end
735