-
Notifications
You must be signed in to change notification settings - Fork 3
/
structure.lua
344 lines (321 loc) · 10.9 KB
/
structure.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
require "intellisense"
local program = require "program"
local M = {}
---@type PlcData[]
global.plc_structures = global.plc_structures or {}
function M.fill_prog_list(struct)
struct.data.command_dropdown = {strings={},link={}}
for i, cmd in ipairs(program.commandList) do
table.insert(struct.data.command_dropdown.strings, i, cmd.disp)
table.insert(struct.data.command_dropdown.link, i, cmd)
end
end
---Populates a new empty struct table
---@return PlcData
local function new_structure()
---@type PlcData
local struct = {
entities = {
main = nil,
input = nil,
output = nil,
},
program = {
inputs = {},
outputs = {},
variables = {},
constants = {},
commands = {},
},
data = {
command_dropdown = { strings = {}, link = {} },
input_dropdown = { strings = {}, link = {} },
output_dropdown = { strings = {}, link = {} },
inputs = {},
outputs = {},
variables = {},
running = false,
execute_next = true,
cb_funcs = {},
cb_keys = {},
status = { status = "ok", msg = ""},
live_page = nil,
alert_holdoff = 0,
},
}
M.fill_prog_list(struct)
return struct
end
---Get the global table containing all our structure info
---@param index integer|uint -- entity to lookup in the structure table
---@return PlcData
function M.get_structure(index)
if not global.plc_structures[index] then
global.plc_structures[index] = new_structure()
end
return global.plc_structures[index]
end
---Remove the structure info from the global table containing of structures
---@param index integer -- entity to lookup amd remove from the structure table
function M.remove_structure(index)
global.plc_structures[index] = nil
end
---Gets the entire structure list for iterating
---@return PlcData[]
function M.get_all_structures()
return global.plc_structures
end
--- Insert signal into table
---@param container table
---@param signal Signal
local function insertSignal(container, signal)
if not container[signal.signal.type] then
container[signal.signal.type] = {}
end
if container[signal.signal.type][signal.signal.name] then
container[signal.signal.type][signal.signal.name].count = container[signal.signal.type][signal.signal.name].count + signal.count
else
container[signal.signal.type][signal.signal.name] = { signal = signal.signal, count = signal.count }
end
end
---Reads all the input signals from incoming curcuit network
---@param struct PlcData
local function readInputs(struct)
local red_inputs = {}
local green_inputs = {}
if struct.entities.input then
-- get red signals
local network = struct.entities.input.get_circuit_network(defines.wire_type.red)
if network and network.signals then
for _, signal in pairs(network.signals) do
insertSignal(red_inputs, signal)
end
end
-- get green signals
network = struct.entities.input.get_circuit_network(defines.wire_type.green)
if network and network.signals then
for _, signal in pairs(network.signals) do
insertSignal(green_inputs, signal)
end
end
end
-- now we have all the signal inputs and we need to transfer the input definitions to the variables area
for _, input in pairs(struct.program.inputs) do
if input and input.signal and input.name then
local wire = input.wire or "none" -- the wire to get signal from
local type = input.signal.type
local name = input.signal.name
local count = 0
if wire ~= "left" then -- is this signal getting read from the red wire
if red_inputs[type] and red_inputs[type][name] then
-- there is a signal for the given filter - save it
count = count + red_inputs[type][name].count
end
end
if wire ~= "right" then -- is this signal getting read from the green wire
if green_inputs[type] and green_inputs[type][name] then
count = count + green_inputs[type][name].count
end
end
input.value = count
end
end
end
---Write all the output signals to outgoing curcuit network
---@param struct PlcData
local function writeOutputs(struct)
if struct.entities.output and struct.program.outputs then
local index = 1;
--- @type LuaConstantCombinatorControlBehavior
local behaviour = struct.entities.output.get_or_create_control_behavior()
if behaviour == nil then
return
end
for _, output in pairs(struct.program.outputs) do
if output and output.signal and output.name and output.name ~= "" then
-- there is a signal for the given filter - save it
behaviour.set_signal(index, { signal = output.signal, count = math.floor(output.value)})
index = index + 1
end
end
end
end
---comment
---@param event EventData.on_tick
function M.on_tick(event)
local structures = M.get_all_structures()
local to_remove = {}
for key, struct in pairs(structures) do
if struct.entities and struct.entities.main and struct.entities.main.valid then
local status = struct.entities.main.status
local running = struct.data.running
struct.entities.main.active = running
if running and status == defines.entity_status.working then
readInputs(struct) -- first step is sample the inputs
program.tickProgram(struct) -- next we process all the code in the unit
writeOutputs(struct) -- then we output the resulting outputs
end
-- check alerts
struct.data.alert_holdoff = struct.data.alert_holdoff or 0
if (event.tick - struct.data.alert_holdoff > 200) and (struct.data.alert ~= nil) then
create_alert(struct.entities.main, struct.data.alert, game.players[1])
struct.data.alert_holdoff = event.tick
end
else
table.insert(to_remove, key)
end
end
for _, key in ipairs(to_remove) do
M.remove_structure(key)
end
end
local pos_offsets = {
[defines.direction.north] = {
input = { x = -1, y = 0, direction = defines.direction.west },
output = { x = 1, y = 0, direction = defines.direction.east },
search = { x1 = 1.2, x2 = -1.2, y1 = 0.2, y2 = -0.2 },
},
[defines.direction.south] = {
input = { x = 1, y = 0, direction = defines.direction.east },
output = { x = -1, y = 0, direction = defines.direction.west },
search = { x1 = 1.2, x2 = -1.2, y1 = 0.2, y2 = -0.2 },
},
[defines.direction.east] = {
input = { x = 0, y = -1, direction = defines.direction.north },
output = { x = 0, y = 1, direction = defines.direction.south },
search = { x1 = 0.2, x2 = -0.2, y1 = 1.2, y2 = -1.2 },
},
[defines.direction.west] = {
input = { x = 0, y = 1, direction = defines.direction.south },
output = { x = 0, y = -1, direction = defines.direction.north},
search = { x1 = 0.2, x2 = -0.2, y1 = 1.2, y2 = -1.2 },
},
}
---Creates the sub-entities and sets up the main entity
---@param entity LuaEntity
function on_build_structure(entity)
-- Determine the location of the sub entities
local offsets = pos_offsets[entity.direction]
local search_area = {
{ entity.position.x + offsets.search.x1, entity.position.y + offsets.search.y1 },
{ entity.position.x + offsets.search.x2, entity.position.y + offsets.search.y2 }
}
-- create the entities
local input = nil
local output = nil
-- handle blueprint ghosts and existing IO entities preserving circuit connections
local ghosts = entity.surface.find_entities(search_area)
for _, ghost in pairs(ghosts) do
if ghost.valid then
if ghost.name == "entity-ghost" then
if ghost.ghost_name == "plc-input" then
_, input = ghost.revive()
elseif ghost.ghost_name == "plc-output" then
_, output = ghost.revive()
end
elseif ghost.name == "plc-input" then
input = ghost
elseif ghost.name == "plc-output" then
output = ghost
end
end
end
-- create if not existing
if input == nil then
input = entity.surface.create_entity {
name = "plc-input",
position = { entity.position.x + offsets.input.x, entity.position.y + offsets.input.y },
direction = offsets.input.direction,
force = entity.force,
fast_replace = false,
destructible = false,
}
input.operable = false
end
-- create if not existing
if output == nil then
output = entity.surface.create_entity {
name = "plc-output",
position = { entity.position.x + offsets.output.x, entity.position.y + offsets.output.y },
direction = offsets.output.direction,
force = entity.force,
fast_replace = false,
destructible = false,
}
output.operable = false
end
-- if the sub-entities were not both created we just destroy it all and bail
if input == nil or output == nil then
if input then input.destroy() end
if output then output.destroy() end
M.remove_structure(entity.unit_number) -- remove it if it exists
entity.destroy()
return
end
-- get and setup structure object
local structure = M.get_structure(entity.unit_number)
if not structure then
entity.destroy()
return
end
structure.entities = {main = entity, input = input, output = output, }
for line = 1, 8 do
structure.program.inputs[line] = { type = "invalid", value = 0 }
end
for line = 1, 8 do
structure.program.outputs[line] = { type = "invalid", value = 0 }
end
for line = 1, 8 do
structure.program.variables[line] = { type = "invalid", value = 0 }
end
for line = 1, 100 do
structure.program.commands[line] = { command = program.commandList[1] }
end
update_param_lists(structure)
structure.data.running = false
structure.data.execute_next = true
end
---comment
---@param event EventData.on_built_entity|EventData.on_robot_built_entity|EventData.script_raised_built|EventData.script_raised_revive
function M.on_built_entity(event)
local entity = event.created_entity or event.entity
if entity and entity.name == "plc-unit" then
on_build_structure(entity)
end
end
---comment
---@param event EventData.on_entity_cloned
function M.on_entity_cloned(event)
local source_entity = event.source
local dest_entity = event.destination
if dest_entity.name == "plc-unit" and source_entity.name == "plc-unit" then
-- allow the build to proceed
on_build_structure(dest_entity)
-- overwrite settings
local old_struct = M.get_structure(source_entity.unit_number)
local new_struct = M.get_structure(dest_entity.unit_number)
if old_struct and new_struct then
new_struct.program = table.deepcopy(old_struct.program)
new_struct.data = table.deepcopy(old_struct.data)
end
end
end
---comment
---@param event EventData.on_robot_pre_mined|EventData.on_pre_player_mined_item|EventData.on_entity_died
function M.on_entity_removed(event)
if not event.entity or not event.entity.valid or event.entity.name ~= "plc-unit" then
return
end
local parts = event.entity.surface.find_entities_filtered{
area = {
{event.entity.position.x - 1.5, event.entity.position.y - 1.5},
{event.entity.position.x + 1.5, event.entity.position.y + 1.5}
},
name = {"plc-input", "plc-output"},
}
for _, part in pairs(parts) do
part.destroy()
end
M.remove_structure(event.entity.unit_number)
end
return M