-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcontrol.lua
More file actions
313 lines (273 loc) · 11.4 KB
/
control.lua
File metadata and controls
313 lines (273 loc) · 11.4 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
local event_handler = require("src/events/event_handler")
local constants = require("src/core/constants")
local bridge = require("src/services/bridge")
local init = require("src/ai_combinator/init")
local update = require("src/ai_combinator/update")
local circuit_network = require("src/core/circuit_network")
local memory = require("src/ai_combinator/memory")
local ai_operation_manager = require("src/core/ai_operation_manager")
local combinator_service = require("src/ai_combinator/combinator_service")
-- Event modules
local blueprint_events = require("src/events/blueprint_events")
local entity_events = require("src/events/entity_events")
local hotkey_events = require("src/events/hotkey_events")
local guis = require("src/gui/gui")
local dialog_manager = require("src/gui/dialogs/dialog_manager")
local ai_bridge_warning_dialog = require("src/gui/dialogs/ai_bridge_warning_dialog")
local vars_dialog = require("src/gui/dialogs/vars_dialog")
local util = require("src/core/utils")
local runtime = require("src/ai_combinator/runtime")
local gui_updater = require("src/gui/gui_updater")
-- Include for registering of event handlers
require("src/testing/testing")
-- ----- Register entity and blueprint event handlers -----
blueprint_events.register()
entity_events.register()
hotkey_events.register()
-- ----- on_tick handling - lua code, gui updates -----
local function on_tick(ev)
-- Receive UDP packets and trigger processing
helpers.recv_udp()
local tick = ev.tick
for uid, combinator in pairs(storage.combinators) do
local combinator_env = memory.combinators[uid]
if not combinator_env then
combinator_env = init.combinator_init(combinator.e)
end
if combinator.removed_by_player then -- if it was removed by the user, keep it so it can be restored from undo
goto skip
end
if not (combinator_env and combinator.e.valid and combinator.out_red.valid and combinator.out_green.valid) then
init.combinator_remove(uid)
goto skip
end
local err_msg = runtime.format_error_message(combinator)
if err_msg then
if tick % constants.LOGIC_ALERT_INTERVAL == 0 then
runtime.alert_about_error(combinator_env, err_msg)
end
goto skip -- suspend combinator logic until errors are addressed
elseif combinator_env._alert then
runtime.alert_clear(combinator_env)
end
if
combinator.irq
and (combinator.irq_tick or 0) < tick - (combinator.irq_delay or 0)
and combinator.e.get_signal(
combinator.irq,
defines.wire_connector_id.combinator_input_green,
defines.wire_connector_id.combinator_input_red
)
~= 0
then
combinator.irq_tick = tick
combinator.next_tick = nil
end
if tick >= (combinator.next_tick or 0) and combinator_env._func then
runtime.run_combinator_tick(combinator, combinator_env, tick)
for _, p in ipairs(game.connected_players) do
local player, vars_uid = game.players[p.index], storage.guis_player["vars." .. p.index]
if not player or vars_uid ~= uid then
goto skip_vars
end
vars_dialog.update(player, uid)
::skip_vars::
end
end
::skip::
end
if next(storage.guis) then
gui_updater.update_signals_in_guis(runtime.format_error_message)
end
end
event_handler.add_handler(defines.events.on_tick, on_tick)
event_handler.add_handler(constants.events.on_task_request_completed, function(event)
-- Complete the AI operation using the new manager
ai_operation_manager.complete_operation(event.uid)
-- Check if response starts with ERROR:
if event.response and event.response:sub(1, 6) == "ERROR:" then
local combinator = storage.combinators[event.uid]
local combinator_env = memory.combinators[event.uid]
if combinator and combinator_env then
-- Extract the error message after "ERROR: "
local error_message = event.response:sub(8) -- Skip "ERROR: "
combinator.state = "error"
combinator.err_parse = "AI Error: " .. error_message
-- Update the LED to show error state
update.update_led(combinator, combinator_env)
game.print("[color=red]AI Error: " .. error_message .. "[/color]")
end
else
combinator_service.save_code(event.uid, event.response, "ai_generation")
end
end)
event_handler.add_handler(constants.events.on_test_generation_completed, function(event)
-- Complete the AI operation using the new manager
ai_operation_manager.complete_operation(event.uid)
end)
-- ----- GUI events and entity interactions -----
script.on_event(defines.events.on_gui_opened, function(ev)
if not ev.entity then
return
end
local player = game.players[ev.player_index]
local e = player.opened
if not (e and e.name == "ai-combinator") then
return
end
if not storage.combinators[e.unit_number] then
player.opened = nil
return util.console_warn(player, "BUG: Combinator #" .. e.unit_number .. " is not registered with mod code")
end
local gui_t = storage.guis[e.unit_number]
if not gui_t then
guis.open(player, e)
else
if gui_t.gui and gui_t.gui.valid then
local other_player_index = gui_t.gui.player_index or 0
-- Only warn if it's actually a different player
if other_player_index ~= ev.player_index then
local other_player = game.players[other_player_index]
local player_name = other_player and other_player.name or "Another player"
player.print(player_name .. " already opened this combinator", { 1, 1, 0 })
end
-- Same player trying to open again - do nothing
else
-- GUI reference is stale, clean up and reopen
guis.close(e.unit_number, ev.player_index)
guis.open(player, e)
end
end
end)
-- ----- Remote Interface for /measured-command benchmarking -----
-- Usage: /measured-command remote.call('ai-combinator', 'run', 1234, 100)
local remote_err = function(msg, ...)
for _, p in pairs(game.players) do
p.print(("AI Combinator remote-call error: " .. msg):format(...), { 1, 1, 0 })
end
end
remote.add_interface("ai-combinator", {
run = function(uid_raw, count)
local uid = tonumber(uid_raw)
local combinator, combinator_env = storage.combinators[uid], memory.combinators[uid]
if not combinator or not combinator_env then
return remote_err("cannot find combinator with uid=%s", uid_raw)
end
local err_n, st, err = 0, nil, nil
for _ = 1, tonumber(count) or 1 do
st, err = pcall(combinator_env._func)
if not st then
err_n = err_n + 1
end
end
if err_n > 0 then
remote_err("%d/%d run(s)" .. " raised error(s), last one: %s", err_n, count, err)
end
end,
})
-- ----- Init -----
local function update_signal_types_table()
storage.signals, storage.signals_short = {}, {} -- short=false for ambiguous ones
local sig_str, sig
for k, sig in pairs(prototypes.virtual_signal) do
if sig.special then
goto skip
end -- anything/everything/each
---@diagnostic disable-next-line: missing-fields
sig_str, sig = circuit_network.cn_sig_str("virtual", k), { type = "virtual", name = k, quality = "normal" }
if sig_str then
storage.signals_short[k], storage.signals[sig_str] = sig_str, sig
end
::skip::
end
for t, protos in pairs({ fluid = prototypes.fluid, item = prototypes.get_item_filtered({ { filter = "hidden", invert = true } }) }) do
for k, _ in pairs(protos) do
---@diagnostic disable-next-line: missing-fields
sig_str, sig = circuit_network.cn_sig_str(t, k), { type = t, name = k, quality = "normal" }
if sig_str then
storage.signals_short[k] = storage.signals_short[k] == nil and sig_str or false
storage.signals[sig_str] = sig
end
end
end
for t, _ in pairs(prototypes.recipe) do
---@diagnostic disable-next-line: missing-fields
sig_str, sig = circuit_network.cn_sig_str("recipe", t), { type = "recipe", name = t, quality = "normal" }
if sig_str then
if storage.signals_short[t] == nil then
storage.signals_short[t] = sig_str
end
storage.signals[sig_str] = sig
end
end
end
local function update_signal_quality_table()
storage.quality = {}
for t, _ in pairs(prototypes.quality) do
table.insert(storage.quality, t)
end
end
local function update_recipes()
for _, force in pairs(game.forces) do
if force.technologies["ai-combinator"].researched then
force.recipes["ai-combinator"].enabled = true
end
end
end
script.on_init(function()
update_signal_quality_table()
update_signal_types_table()
for k, _ in pairs(util.tt("combinators presets guis guis_player")) do
storage[k] = {}
end
end)
script.on_load(function()
-- Check if AI bridge is available when mod is loaded
bridge.check_bridge_availability()
end)
script.on_configuration_changed(function(data) -- migration
update_signal_quality_table()
update_signal_types_table()
local update = data.mod_changes and data.mod_changes[script.mod_name]
if update and update.old_version then
-- TODO add migration code here if needed
end
update_recipes()
-- Check if AI bridge is available after configuration changes
bridge.check_bridge_availability()
end)
-- Add console command to test AI bridge ping
commands.add_command("ai-ping", "Send a ping request to the AI bridge", function(command)
local uid = tonumber(command.parameter) or 0
bridge.send_ping_request(uid)
game.print("Ping request sent to AI bridge (uid: " .. uid .. ")")
end)
-- Add event handler for ping responses
event_handler.add_handler(constants.events.on_ping_response, function(payload)
-- Only print message for manual console commands, not automatic bridge checks
if (payload.uid or 0) ~= constants.BRIDGE_CHECK_UID then
game.print("Received ping response (uid: " .. (payload.uid or 0) .. ", status: " .. (payload.status or "unknown") .. ")")
end
end)
event_handler.add_handler(constants.events.on_bridge_check_completed, function(payload)
if not payload.available then
-- Show warning window to all players
for _, player in pairs(game.players) do
if player.valid then
ai_bridge_warning_dialog.show(player.index, true)
end
end
end
end)
-- Check bridge availability when players join
event_handler.add_handler(defines.events.on_player_joined_game, function(_)
bridge.check_bridge_availability()
end)
-- Clean up dialog stacks when players are removed
event_handler.add_handler(defines.events.on_player_removed, function(event)
dialog_manager.cleanup_player(event.player_index)
end)
-- Activate Global (Storage) Variable Viewer (gvv) mod, if installed/enabled - https://mods.factorio.com/mod/gvv
if script.active_mods["gvv"] then
require("__gvv__.gvv")()
end