-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpipe_dream.lua
2379 lines (2026 loc) · 75.4 KB
/
pipe_dream.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
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local PrimeUI = require "primeui"
local thready = require "thready"
local file_helper = require "file_helper"
local logging = require "logging"
local data_dir = file_helper:instanced("data")
local log = logging.create_context("pipe_dream")
if ... == "debug" then
logging.set_level(logging.LOG_LEVEL.DEBUG)
end
local main_win = window.create(term.current(), 1, 1, term.getSize())
local width, height = term.getSize()
local logging_display_win = window.create(term.current(), 3, 8, width - 4, height - 8)
local logging_win = window.create(logging_display_win, 1, 1, width - 4, height - 7)
logging_display_win.setVisible(false)
main_win.setVisible(false)
logging.set_window(logging_win)
local NICKNAMES_FILE = "nicknames.txt"
local FLUID_MOVED_FILE = "fluid_moved.txt"
local ITEMS_MOVED_FILE = "items_moved.txt"
local CONNECTIONS_FILE = "connections.txt"
local MOVING_ITEMS_FILE = "moving_items.txt"
local UPDATE_TICKRATE_FILE = "update_tickrate.txt"
local nicknames = data_dir:unserialize(NICKNAMES_FILE, {}) ---@type table<string, string>
local fluid_moved = data_dir:unserialize(FLUID_MOVED_FILE, 0) ---@type integer
local items_moved = data_dir:unserialize(ITEMS_MOVED_FILE, 0) ---@type integer
local connections = data_dir:unserialize(CONNECTIONS_FILE, {}) ---@type Connection[]
local moving_items = data_dir:unserialize(MOVING_ITEMS_FILE, true) ---@type boolean
local update_tickrate = data_dir:unserialize(UPDATE_TICKRATE_FILE, 10) ---@type integer
log.info("Loaded data (or created defaults).")
local function save()
data_dir:serialize(NICKNAMES_FILE, nicknames)
data_dir:serialize(FLUID_MOVED_FILE, fluid_moved)
data_dir:serialize(ITEMS_MOVED_FILE, items_moved)
data_dir:serialize(CONNECTIONS_FILE, connections)
data_dir:serialize(MOVING_ITEMS_FILE, moving_items)
data_dir:serialize(UPDATE_TICKRATE_FILE, update_tickrate)
log.info("Saved data.")
end
-- We are fine if this value fails to load, as it will not break the program.
if type(items_moved) ~= "number" then
items_moved = 0
log.warn("Items moved file is corrupted, resetting to 0.")
end
---@cast items_moved number
-- We are also fine if this value fails to load, as it will not break the program.
if type(fluid_moved) ~= "number" then
fluid_moved = 0
log.warn("Fluid moved file is corrupted, resetting to 0.")
end
-- We are also fine if this value fails to load, as it will not break the program.
if type(update_tickrate) ~= "number" then
update_tickrate = 10
log.warn("Update tickrate file is corrupted, resetting to 10.")
end
---@cast update_tickrate number
-- We are also also fine if this value fails to load, as it will not break the program.
if type(nicknames) ~= "table" then
nicknames = {}
log.warn("Nicknames file is corrupted, resetting to empty table.")
end
-- We are not fine if this value fails to load, as it will break the program.
if type(connections) ~= "table" then
error("Connections file might be corrupted, please check it for errors. Cannot read it currently.", 0)
end
---@cast connections Connection[]
---@class Connection
---@field name string The name of the connection.
---@field from string The peripheral the connection is from.
---@field to string[] The peripherals the connection is to.
---@field filter_list string[] The blacklist or whitelist of items.
---@field filter_mode "whitelist"|"blacklist" The item filter mode of the connection.
---@field mode "1234"|"split" The mode of the connection. 1234 means "push and fill 1, then 2, then 3, then 4". Split means "split input evenly between all outputs".
---@field moving boolean Whether the connection is active (moving items).
---@field id integer The unique ID of the connection.
--- Create an information box.
---@param win Window The window to draw the box on.
---@param title string The title of the box.
---@param desc string The description of the box.
---@param height integer The height of the box.
---@param override_title_color integer? The color of the title text.
local function info_box(win, title, desc, height, override_title_color)
local width = win.getSize()
width = width - 4
-- Info box with border
PrimeUI.borderBox(win, 3, 3, width, height)
PrimeUI.textBox(win, 3, 2, #title + 2, 1, ' ' .. title .. ' ', override_title_color or colors.purple)
PrimeUI.textBox(win, 3, 3, width, height, desc, colors.lightGray)
end
--- Create a selection box, with a list of items.
---@param win Window The window to draw the box on.
---@param x integer The x position of the box.
---@param y integer The y position of the box.
---@param width integer The width of the box.
---@param height integer The height of the box.
---@param items string[] The items to display in the box.
---@param action string|fun(index: integer, scroll_index: integer) The action to perform when an item is selected.
---@param select_change_action nil|string|fun(index: integer, scroll_index: integer) The action to perform when the selection changes.
---@param fg_color integer The color of the text.
---@param bg_color integer The color of the background.
---@param initial_index integer The index of the item to select initially.
---@param initial_scroll integer The index of the item to scroll to initially.
---@param disabled boolean? Whether the box is disabled (displayed, but not interactable).
local function outlined_selection_box(win, x, y, width, height, items, action, select_change_action, fg_color, bg_color,
initial_index, initial_scroll, disabled)
-- Selection box with border
PrimeUI.borderBox(win, x, y, width, height, fg_color, bg_color)
-- Draw the items
return PrimeUI.selectionBox(win, x, y, width + 1, height, items, action, select_change_action, fg_color, bg_color,
initial_index, initial_scroll, disabled)
end
--- Create an outlined input box.
---@param win Window The window to draw the box on.
---@param x integer The x position of the box.
---@param y integer The y position of the box.
---@param width integer The width of the box.
---@param action string|fun(text: string) The action to perform when the input is submitted.
---@param fg_color integer The color of the text.
---@param bg_color integer The color of the background.
---@param replacement string? The replacement character for the input.
---@param history string[]? The history of inputs.
---@param completion_func nil|fun(text: string): string[] The function to call for completion.
---@param default string? The default text to display in the input box.
---@param disabled boolean? Whether the box is disabled (displayed, but not interactable).
---@return string[] buffer The input buffer.
local function outlined_input_box(win, x, y, width, action, fg_color, bg_color, replacement, history, completion_func,
default, disabled)
-- Input box with border
PrimeUI.borderBox(win, x, y, width, 1, fg_color, bg_color)
return PrimeUI.inputBox(win, x, y, width, action, fg_color, bg_color, replacement, history, completion_func, default,
disabled)
end
--- Get all peripherals by their name
---@return string[] peripherals The list of peripheral names.
local function get_peripherals()
local peripherals = peripheral.getNames()
-- Replace names with nicknames
for i, v in ipairs(peripherals) do
peripherals[i] = v
end
--[[@fixme this code needs to be re-added
local periph_lookup = {}
for i, v in ipairs(peripherals) do
periph_lookup[v] = true
end
-- Iterate through connections and add any peripherals from that list that
-- have been disconnected.
for name in pairs(connections) do
if not periph_lookup[name] then
table.insert(peripherals, "dc:" .. (nicknames[name] or name))
end
end
]]
return peripherals
end
--- Clear the screen buffer and ready it for drawing.
local function clear()
main_win.setVisible(false)
main_win.setBackgroundColor(colors.black)
main_win.clear()
PrimeUI.clear()
end
--- Display the unacceptable input screen.
---@param _type "error"|"input"|string The type of error.
---@param reason string The reason for the error.
local function unacceptable(_type, reason)
log.warn("Unacceptable :", _type, ":", reason)
clear()
-- Draw info box.
if _type == "error" then
info_box(
main_win,
"Error",
("An error occurred.\n%s\n\nPress enter to continue."):format(reason),
15,
colors.red
)
elseif _type == "input" then
info_box(
main_win,
"Input Error",
("The last user input was unacceptable.\n%s\n\nPress enter to continue."):format(reason),
15,
colors.red
)
else
info_box(
main_win,
"Unknown Error",
("An unknown error occurred.\n%s\n\nPress enter to continue."):format(reason),
15,
colors.red
)
end
PrimeUI.keyAction(keys.enter, "exit")
PrimeUI.keyAction(keys.tab, "exit")
main_win.setVisible(true)
PrimeUI.run()
end
---@type table<integer, true> A lookup table of keys that are currently held.
local keys_held = {}
--- Key listener thread. Waits for `key` or `key_up` events and updates the `keys_held` table.
local function key_listener()
keys_held = {} -- Reset the keys held when this method is called.
while true do
local event, key = os.pullEvent()
if event == "key" then
keys_held[key] = true
elseif event == "key_up" then
keys_held[key] = nil
end
end
end
local function shift_held()
return keys_held[keys.leftShift] or keys_held[keys.rightShift]
end
--- Verify a connection.
---@param connection_data Connection The connection data to verify.
---@return boolean valid Whether the connection is valid.
---@return string? error_message The error message if the connection is invalid.
local function verify_connection(connection_data)
if not connection_data then
return false, "Connection data is nil. This should not happen, and is a bug."
end
if not connection_data.name or connection_data.name == "" then
return false, "Connection has no name."
end
if not connection_data.from or connection_data.from == "" then
return false, "Origin not set."
end
if not connection_data.to or #connection_data.to == 0 then
return false, "No destinations selected."
end
if not connection_data.filter_mode or (connection_data.filter_mode ~= "whitelist" and connection_data.filter_mode ~= "blacklist") then
return false, "Filter mode is not set or is invalid."
end
if not connection_data.filter_list then
return false, "Filter list is not set."
end
if not connection_data.mode or (connection_data.mode ~= "1234" and connection_data.mode ~= "split") then
return false, "Mode is not set or is invalid."
end
return true, "Connection is valid, so you should not see this message. This is a bug if you do."
end
--- Confirmation menu with custom title and body.
---@param title string The title of the menu.
---@param body string The body of the menu.
---@param select_yes_default boolean? Whether the default selection is "Yes".
---@return boolean sure Whether the user is sure they want to exit without saving.
local function confirmation_menu(title, body, select_yes_default)
while true do
clear()
-- Draw info box.
info_box(main_win, title, body, 2)
outlined_selection_box(
main_win, 3, 7, width - 4, 2,
{
"Yes",
"No"
}, "selection", nil,
colors.white, colors.black,
select_yes_default and 1 or 2, 1
)
PrimeUI.keyAction(keys.tab, "exit")
main_win.setVisible(true)
local object, event, result = PrimeUI.run()
if object == "selectionBox" then
if event == "selection" then
return result == "Yes"
end
elseif object == "keyAction" and event == "exit" and shift_held() then
return false
end
end
end
--- Ask the user if they're sure they want to exit without saving.
---@return boolean sure Whether the user is sure they want to exit without saving.
local function confirm_exit_no_save()
return confirmation_menu(
"Exit Without Saving",
"Are you sure you want to exit without saving?",
false
)
end
--- Implement the connection filter editing menu.
---@param connection_data Connection The connection data to edit.
local function _connections_filter_edit_impl(connection_data)
--[[
# Filter connectionname ##############################
# > Add item # -- this box will turn into an info box if add/view/remove is selected
# View items #
# Remove item #
# Toggle blacklist/whitelist #
######################################################
# Filter blacklist ################################### -- or whitelist...
# minecraft:item_1 # -- If possible, the filter preview should scroll up and down if overfull
# minecraft:item_2 # -- I believe we can use PrimeUI.addTask to do this, just have something
# ... # -- resolve PrimeUI after half a second or so?
######################################################
]]
log.debug("Editing filter for connection", connection_data.name)
local items = connection_data.filter_list
local item_count = #items
table.sort(items)
---@type "add"|"view"|"remove"|nil The selected action.
local selected
---@type integer, integer The selected item in the preview box. Will be set to -1 unless it is active.
local item_selected, item_scroll = -1, 1
local items_y, items_height = 9, 10
---@type integer, integer For the main selection box.
local main_selected, main_scroll = 1, 1
---@type integer When at either "edge" of the list, pause for this many iterations before reversing direction.
local scroll_edge_pause = 5
---@type 1|0|-1 The direction to scroll in.
local scroll_direction = 0
---@type 1|-1 The direction to swap to scrolling after the edge pause.
local next_scroll_direction = 1
local add_item, view_items, remove_item, toggle_mode = "Add item",
"View items",
"Remove item",
"Toggle blacklist/whitelist"
local no_items_toggle = true
local timer_timeout = 0.5
local timer
--- Start a new timer.
local function new_timer()
timer = os.startTimer(timer_timeout)
end
--- Insert an item into the filter list.
---@param item string The item name to insert.
local function insert_item(item)
if not item or item == "" then
log.debug("Add empty item, ignored.")
return
end
table.insert(items, item)
item_count = item_count + 1
log.debug("Added item", item, "to filter list for connection", connection_data.name)
table.sort(items)
end
--- Ask the user if they really want to remove the given item, and then remove it if they do.
---@param selection integer The index of the item to remove.
local function really_remove(selection)
-- Code cleanup: Declare the title and body outside of the if statement to reduce its size.
local title = "Remove item"
local body = "Are you sure you want to remove item " .. tostring(items[selection]) .. " from the filter list?"
if items[selection] and confirmation_menu(title, body) then
log.debug("Remove item", items[selection], "from filter list for connection", connection_data.name)
table.remove(items, selection)
item_count = item_count - 1
-- Offset the selected item, since we just removed one.
item_selected = item_selected - 1
if item_selected < 1 then
item_selected = 1
end
if item_selected < item_scroll then
item_scroll = item_selected
end
end
end
new_timer()
while true do
clear()
---@type string[] The buffer for the input box.
local buffer = {}
-- If we've selected something, we can draw the info box for it.
if selected == "add" then
info_box(
main_win,
"Add item",
"Enter the name of the item to add to the filter list, then press enter to confirm.",
2
)
buffer = outlined_input_box(
main_win, 3, 7, width - 4,
"add-item",
colors.white, colors.black
)
PrimeUI.textBox(
main_win, 3, 6, 11, 1,
" Item Name ",
colors.purple
)
items_y = 10
items_height = 9
elseif selected == "view" then
info_box(
main_win,
"View items",
"Press shift+tab to go back.",
1
)
items_y = 6
items_height = 13
elseif selected == "remove" then
info_box(
main_win,
"Remove item",
"Select the item to remove from the filter list.",
1
)
items_y = 6
items_height = 13
else
items_y = 13
items_height = 6
info_box(
main_win,
"Filter - " .. connection_data.name,
"Select an action to perform on the filter list.\nPress shift+tab to save and exit.",
2
)
-- No info box, just put the selection box in.
outlined_selection_box(
main_win, 3, 7, width - 4, 4,
{
add_item,
view_items,
remove_item,
toggle_mode
},
"select", "select-change",
colors.white, colors.black,
main_selected, main_scroll
)
end
local enable_selector = selected == "view" or selected == "remove"
-- Draw the preview selection box
outlined_selection_box(
main_win,
3, items_y,
width - 4, items_height,
#items == 0 and { no_items_toggle and "No items" or "" } or items,
"select-item", "select-item-change",
enable_selector and colors.white or colors.gray, colors.black,
item_selected, item_scroll,
not enable_selector
)
PrimeUI.textBox(
main_win,
3, items_y - 1,
2 + #connection_data.filter_mode, 1,
' ' .. connection_data.filter_mode .. ' ',
enable_selector and colors.purple or colors.gray
)
if selected ~= "add" then -- Add stops working due to read implementation
PrimeUI.addTask(function()
repeat
local _, timer_id = os.pullEvent("timer")
until timer_id == timer
no_items_toggle = not no_items_toggle
PrimeUI.resolve("scroller")
end)
end
PrimeUI.keyAction(keys.tab, "exit")
main_win.setVisible(true)
local object, event, result, selection, scroll_result = PrimeUI.run()
local function reset_scroller()
scroll_direction = 0
next_scroll_direction = 1
scroll_edge_pause = 5
item_scroll = 1
item_selected = -1
end
if object == "selectionBox" then
if event == "select-change" then
main_selected = selection
main_scroll = scroll_result
elseif event == "select-item-change" then
item_selected = selection
item_scroll = scroll_result
elseif event == "select" then
if result == add_item then
selected = "add"
log.debug("Selected add item")
elseif result == view_items then
selected = "view"
item_selected = 1
item_scroll = 1
log.debug("Selected view items")
elseif result == remove_item then
selected = "remove"
item_selected = 1
item_scroll = 1
log.debug("Selected remove item")
elseif result == toggle_mode then
connection_data.filter_mode = connection_data.filter_mode == "whitelist" and "blacklist" or "whitelist"
log.debug("Toggled filter mode to", connection_data.filter_mode)
end
elseif event == "select-item" then
if selected == "remove" then
really_remove(selection)
-- Exit the selection mode
selected = nil
reset_scroller()
-- Restart the timer, since we did something that may take longer than 0.5 secs
new_timer()
end
end
elseif object == "scroller" then
-- scroll the preview box
new_timer()
if not enable_selector and item_count > items_height then
item_scroll = item_scroll + scroll_direction
if item_scroll < 1 then
item_scroll = 1
scroll_direction = 0
next_scroll_direction = 1
scroll_edge_pause = 5
elseif item_scroll > item_count - items_height + 1 then
item_scroll = item_count - items_height + 1
scroll_direction = 0
next_scroll_direction = -1
scroll_edge_pause = 5
end
if scroll_edge_pause > 0 then
scroll_edge_pause = scroll_edge_pause - 1
else
scroll_direction = next_scroll_direction
end
end
elseif object == "keyAction" and event == "exit" then
if shift_held() then
if selected then
-- Something was selected, so go back to the "main" section, reverting
-- data to defaults.
selected = nil
reset_scroller()
else
save()
return
end
else
if selected then
if selected == "add" then
insert_item(table.concat(buffer))
elseif selected == "remove" then
really_remove(item_selected)
-- Exit the selection mode
selected = nil
reset_scroller()
end -- "view" doesn't need anything done here.
-- Restart the timer, since we did something that may take longer than 0.5 secs
new_timer()
else
---@fixme
-- I don't like this. If we change the order of these selections in
-- the future, we have to change this code too.
-- Can we un-hardcode the 1/2/3/4 here?
-- Select the item
if main_selected == 1 then
selected = "add"
log.debug("Selected add item")
elseif main_selected == 2 then
selected = "view"
item_selected = 1
item_scroll = 1
log.debug("Selected view items")
elseif main_selected == 3 then
selected = "remove"
item_selected = 1
item_scroll = 1
log.debug("Selected remove item")
elseif main_selected == 4 then
connection_data.filter_mode = connection_data.filter_mode == "whitelist" and "blacklist" or "whitelist"
log.debug("Toggled filter mode to", connection_data.filter_mode)
end
end
end
elseif object == "inputBox" then
if event == "add-item" then
insert_item(result)
selected = nil
reset_scroller()
-- Restart the timer, since we did something that may take longer than 0.5 secs
new_timer()
end
end
end
end
--- Implement the connection editing menu.
---@param connection_data Connection? The connection data to edit.
local function _connections_edit_impl(connection_data)
--[[
# Add Connection ##################################### -- Sections will expand/contract as needed.
# Enter the name of this connection # -- Info box will change depending on expanded section.
# Press enter when done. #
######################################################
# Name ###############################################
# blablabla #
######################################################
# Origin #############################################
# peripheral_1 #
######################################################
# Destinations #######################################
# peripheral_2 #
# peripheral_3 #
# ... #
######################################################
# Filter Mode ########################################
# Whitelist #
# Blacklist #
######################################################
# Filters ############################################
# item_1 #
# item_2 #
# ... #
######################################################
# Mode ###############################################
# Fill 1, then 2, then 3, then 4 #
# Split evenly #
######################################################
]]
local _connection_data = {
name = "",
from = "",
to = {},
filter_list = {},
filter_mode = "blacklist",
mode = "1234",
moving = true, -- New connections are enabled by default.
id = os.epoch("utc")
}
local editing_con = false
if connection_data then
_connection_data.name = connection_data.name or _connection_data.name
_connection_data.from = connection_data.from or _connection_data.from
_connection_data.to = connection_data.to or _connection_data.to
_connection_data.filter_list = connection_data.filter_list or _connection_data.filter_list
_connection_data.filter_mode = connection_data.filter_mode or _connection_data.filter_mode
_connection_data.mode = connection_data.mode or _connection_data.mode
_connection_data.moving = connection_data.moving or _connection_data.moving
_connection_data.id = connection_data.id or _connection_data.id
log.debug("Editing connection", _connection_data.name)
editing_con = true
else
log.debug("Creating new connection")
end
--- If the connection is from a non-inventory type peripheral, it is
--- connection limited, and can only move items to one destination. This means
--- the filter and mode options should be disabled.
local connection_limited = false
local cached_peripheral_list = get_peripherals()
local expanded_section = 1
---@type table<integer, {name: string, display: string}> The list of peripherals with their nicknames.
local periphs_with_nicknames = {}
---@type table<integer, {name: string, display: string}> The list of peripherals with their nicknames.
local destination_periphs_with_nicknames = {}
local sorted_periphs = {}
local sorted_destinations = {}
local section_infos = {
{
name = (
(_connection_data.name == "") and "Name" or
"Name - " .. _connection_data.name
),
info =
"Enter the name of this connection\nPress enter to save section data, tab to go to the next step, and shift+tab to go back.",
size = 1,
object = "input_box",
args = {
default = _connection_data.name,
action = "set-name",
},
increment_on_enter = true,
disable_when_limited = false,
save_buffer = "name",
},
{
name = (
(_connection_data.from == "") and "Origin" or
"Origin - " .. (nicknames[_connection_data.from] or _connection_data.from)
),
info = "Select the peripheral this connection is from.",
size = 7,
object = "selection_box",
args = {
action = "select-origin",
items = sorted_periphs,
},
increment_on_enter = true,
disable_when_limited = false,
save_buffer = false,
},
{
name = (
(#_connection_data.to == 0) and "Destinations" or
"Destinations - " .. (#_connection_data.to) .. " selected"
),
info = "Select the peripherals this connection is to.",
connection_limited_info = "This connection is limited, and can only set a single destination.",
size = 7,
object = "selection_box",
args = {
action = "select-destination",
items = sorted_destinations,
},
increment_on_enter = false,
disable_when_limited = false,
save_buffer = false,
},
{
name = (
(_connection_data.filter_mode == "") and "Filter Mode" or
"Filter Mode - " .. _connection_data.filter_mode
),
info = "Select the filter mode of the connection. The list starts empty, and you can edit it in another menu.",
connection_limited_info = "This connection is limited, filters cannot apply to it.",
size = 2,
object = "selection_box",
args = {
action = "select-filter_mode",
items = { "Blacklist", "Whitelist" },
},
increment_on_enter = true,
disable_when_limited = true,
save_buffer = false,
},
{
name = (
(_connection_data.mode == "") and "Mode" or
"Mode - " .. _connection_data.mode
),
info = "Select the mode of the connection.",
connection_limited_info = "This connection is limited, and can only be to one destination.",
size = 2,
object = "selection_box",
args = {
action = "select-mode",
items = { "Fill 1, then 2, then 3, then 4", "Split evenly" },
},
increment_on_enter = true,
disable_when_limited = true,
save_buffer = false,
}
}
local function save_connection()
local ok, err = verify_connection(_connection_data)
if ok then
-- Search the connections list for a connection with this ID.
for i, v in ipairs(connections) do
if v.id == _connection_data.id then
connections[i] = _connection_data
save()
return true
end
end
-- If we made it here, we didn't find the connection in the list.
-- Thus, this must be a new connection.
-- We can just insert it into the list.
table.insert(connections, _connection_data)
return true
else
unacceptable("input", "Connection data is malformed or incorrect: " .. tostring(err))
return false
end
end
while true do
if expanded_section > #section_infos then
if save_connection() then
return
end
expanded_section = #section_infos
end
local section_info = section_infos[expanded_section]
-- Update peripheral list
-- Clear the list
while periphs_with_nicknames[1] do
table.remove(periphs_with_nicknames)
end
while destination_periphs_with_nicknames[1] do
table.remove(destination_periphs_with_nicknames)
end
-- Add the peripherals to the list
-- Step 1: Add the peripherals to the list
for i, v in ipairs(cached_peripheral_list) do
-- we can just outright add the nicknames here for this table.
periphs_with_nicknames[i] = {
name = v,
display = nicknames[v] or v
}
local found = false
for j = 1, #_connection_data.to do
if _connection_data.to[j] == v then
destination_periphs_with_nicknames[i] = {
name = v,
display = j .. ". " .. periphs_with_nicknames[i].display
}
found = true
break
end
end
if not found then
destination_periphs_with_nicknames[i] = {
name = periphs_with_nicknames[i].name,
display = " " .. periphs_with_nicknames[i].display
}
end
end
-- Step 2: Sort the peripheral lists.
table.sort(periphs_with_nicknames, function(a, b)
return a.display < b.display
end)
table.sort(destination_periphs_with_nicknames, function(a, b)
return a.display < b.display
end)
-- Step 3: Create the list of sorted peripherals.
-- 3.a) Clear the list
while sorted_periphs[1] do
table.remove(sorted_periphs)
end
while sorted_destinations[1] do
table.remove(sorted_destinations)
end
-- 3.b) Add the peripherals to the list
for i, v in ipairs(periphs_with_nicknames) do
sorted_periphs[i] = v.display
end
for i, v in ipairs(destination_periphs_with_nicknames) do
sorted_destinations[i] = v.display
end
-- ALL OF THIS WAS NEEDED JUST TO SORT THE PERIPHERALS WITH NICKNAMES
-- SO THAT I COULD STILL REFERENCE THEM BY THEIR ORIGINAL NAME LATER
-- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHH
-- Begin drawing
clear()
-- Draw info box.
info_box(
main_win,
editing_con and "Edit Connection" or "Add Connection",
connection_limited and section_info.connection_limited_info or section_info.info,
3
)
local y = 8
local input_box_buffer
-- Draw the sections
for i = 1, #section_infos do
local section = section_infos[i]
local expanded = i == expanded_section
local color = expanded and colors.purple or colors.gray
local text = ' ' .. section.name .. ' '
if section.disable_when_limited and connection_limited then
text = ' ' .. section.name:gsub(" ?%-.+", "") .. ' ' .. "- Connection Limited "
end
if expanded then
-- Draw the stuffs
local object = section.object
local args = section.args
if object == "input_box" then
-- Input box
input_box_buffer = outlined_input_box(
main_win, 3, y, width - 4,
args.action,
colors.white, colors.black,
nil, nil, nil,
args.default
)
elseif object == "selection_box" then
-- Selection box
if #args.items == 0 then
args.items = { "No peripherals" }
end
outlined_selection_box(
main_win,
3,
y,
width - 4,
section.size,
args.items,
args.action,
nil,
section.disable_when_limited and connection_limited and colors.red or colors.white,
colors.black,
args.selection or 1,