SHOW:
|
|
- or go back to the newest paste.
1 | - | -- JoeyJoeJoeJimbob clever recharge startup |
1 | + | -- Backpack Inventory Processor v1.21 |
2 | -- Configuration | |
3 | - | local apiary=peripheral. Wrap("back") |
3 | + | local trash = peripheral.wrap("up") |
4 | - | local size=apiary.getInventorySize() |
4 | + | local chest = peripheral.wrap("back") |
5 | - | print(apiary.getInventoryName()) |
5 | + | local modem = peripheral.wrap("top") -- Modem on top as per your setup |
6 | - | print(size) |
6 | + | local monitor = peripheral.find("monitor") -- Find the monitor through the modem |
7 | local backpack_side = "front" | |
8 | - | while true do |
8 | + | |
9 | - | for i=1,size do |
9 | + | -- Constants |
10 | - | local slot = apiary.getStackInSlot(i) |
10 | + | local VERSION = "1.21" |
11 | - | if apiary.getStackInSlot(i)~=nil then |
11 | + | local BACKPACKS_PER_CYCLE = 9 -- Process exactly 9 backpacks per cycle |
12 | - | print("slot "..i.." max_dmg="..slot.max_dmg..", dmg="..slot.dmg) |
12 | + | local STUCK_LIMIT = 10 |
13 | - | if slot.dmg>20 then |
13 | + | local SLEEP_INTERVAL = 0.25 |
14 | - | apiary.pushItem("UP",i) |
14 | + | |
15 | - | -- print("Pushed?") |
15 | + | -- Global tables to track item quantities and frequencies (removed local so they're actually global for save functions) |
16 | - | -- tweak long enough for a recharge |
16 | + | item_quantities = {} -- {item_name = total_quantity} |
17 | - | sleep(30) |
17 | + | item_frequencies = {} -- {item_name = number_of_stacks} |
18 | - | end |
18 | + | |
19 | -- wtp imports | |
20 | - | end |
20 | + | local filefunctions = dofile("filefunctions.lua") -- https://pastebin.com/52b6wYBq |
21 | - | sleep(30) |
21 | + | |
22 | - | end |
22 | + | -- Append line of text to file at 'path' |
23 | local function fileAppend(path, text) | |
24 | local file = io.open(path, "a") | |
25 | file:write(text .. "\n") | |
26 | file:close() | |
27 | end | |
28 | -- Append a log entry with timestamp 'time' and text 'text' to the file at 'path' | |
29 | local function addLog(text) | |
30 | fileAppend("log.txt", "[" .. os.time() .. "]"..text) | |
31 | end | |
32 | ||
33 | ||
34 | -- Initialize monitor with a specific text scale | |
35 | local function initMonitor(scale) | |
36 | if not monitor then | |
37 | error("Monitor not found. Ensure it's connected via the modem.") | |
38 | end | |
39 | monitor.clear() | |
40 | monitor.setTextScale(scale) | |
41 | monitor.setCursorPos(1, 1) | |
42 | return monitor.getSize() | |
43 | end | |
44 | ||
45 | -- Display items on monitor in two columns with progress bars at the bottom | |
46 | local function displayManifest(manifest, mon_width, mon_height, backpacks_processed, current_slot, backpack_size) | |
47 | local mon_width, mon_height = initMonitor(1.4) -- Scale 1.4 for processing | |
48 | monitor.clear() | |
49 | monitor.setCursorPos(1, 1) | |
50 | ||
51 | -- Add version header | |
52 | monitor.write("Version " .. VERSION) | |
53 | monitor.setCursorPos(1, 2) | |
54 | monitor.write("Backpack Contents") | |
55 | monitor.setCursorPos(1, 3) | |
56 | ||
57 | local mid_point = math.ceil(mon_width / 2) -- Split the width for two columns | |
58 | local max_item_length = mid_point - 1 -- Dynamically set max_item_length based on column width | |
59 | local row = 3 -- Start below the headers | |
60 | local col = 1 | |
61 | local item_display_height = mon_height - 2 -- Reserve last 2 lines for progress bars | |
62 | ||
63 | for _, item in ipairs(manifest) do | |
64 | if row > item_display_height then | |
65 | col = mid_point -- Second column starts at mid_point | |
66 | row = 3 -- Reset row below the headers | |
67 | monitor.setCursorPos(col, row) | |
68 | end | |
69 | -- Truncate item name to leave room for quantity (": XX" plus buffer) | |
70 | local display_name = item.name | |
71 | local name_length = max_item_length - 5 -- Reserve 5 characters for ": XX" and a buffer | |
72 | if #display_name > name_length then | |
73 | display_name = string.sub(display_name, 1, name_length - 3) .. "..." | |
74 | end | |
75 | monitor.write(display_name .. ": " .. item.count) | |
76 | row = row + 1 | |
77 | monitor.setCursorPos(col, row) | |
78 | end | |
79 | ||
80 | -- Draw backpack progress bar (Line mon_height - 1) | |
81 | local backpack_percent = (backpacks_processed / BACKPACKS_PER_CYCLE) * 100 | |
82 | local filled_backpack = math.floor(backpack_percent * mon_width / 100) | |
83 | monitor.setCursorPos(1, mon_height - 1) | |
84 | monitor.setBackgroundColour(colors.green) | |
85 | for i = 1, filled_backpack do | |
86 | monitor.write(" ") | |
87 | end | |
88 | monitor.setBackgroundColour(colors.red) | |
89 | for i = filled_backpack + 1, mon_width do | |
90 | monitor.write(" ") | |
91 | end | |
92 | monitor.setBackgroundColour(colors.black) | |
93 | monitor.setTextColour(colors.white) | |
94 | monitor.setCursorPos(1, mon_height - 1) | |
95 | monitor.write("Backpacks: " .. math.floor(backpack_percent) .. "%") | |
96 | ||
97 | -- Draw inventory progress bar (Line mon_height) | |
98 | local inventory_percent = backpack_size > 0 and (current_slot / backpack_size) * 100 or 0 | |
99 | local filled_inventory = math.floor(inventory_percent * mon_width / 100) | |
100 | monitor.setCursorPos(1, mon_height) | |
101 | monitor.setBackgroundColour(colors.green) | |
102 | for i = 1, filled_inventory do | |
103 | monitor.write(" ") | |
104 | end | |
105 | monitor.setBackgroundColour(colors.red) | |
106 | for i = filled_inventory + 1, mon_width do | |
107 | monitor.write(" ") | |
108 | end | |
109 | monitor.setBackgroundColour(colors.black) | |
110 | monitor.setTextColour(colors.white) | |
111 | monitor.setCursorPos(1, mon_height) | |
112 | monitor.write("Inventory: " .. math.floor(inventory_percent) .. "%") | |
113 | end | |
114 | ||
115 | -- Check for backpack and handle stuck detection | |
116 | local function checkForBackpack() | |
117 | local stuck = 0 | |
118 | while true do | |
119 | local has_block, data = turtle.inspect() | |
120 | if has_block then | |
121 | return true | |
122 | end | |
123 | print("Waiting for backpack...") | |
124 | sleep(SLEEP_INTERVAL) | |
125 | stuck = stuck + 1 | |
126 | if stuck >= STUCK_LIMIT then | |
127 | print("No more backpacks to process.") | |
128 | return false | |
129 | end | |
130 | end | |
131 | end | |
132 | ||
133 | -- Process backpack contents and track items imported | |
134 | local function processBackpack(backpack, chest, manifest, mon_width, mon_height, backpacks_processed) | |
135 | local size = backpack.size() | |
136 | local items_imported = 0 -- Track items imported in this backpack | |
137 | for i = 1, size do | |
138 | local slot = backpack.getItemDetail(i) | |
139 | if slot then | |
140 | addLog("Slot " .. i .. " contains " .. slot.name .. " (" .. slot.count .. ")") | |
141 | table.insert(manifest, {name = slot.name, count = slot.count}) | |
142 | -- Update item quantities and frequencies | |
143 | item_quantities[slot.name] = (item_quantities[slot.name] or 0) + slot.count | |
144 | item_frequencies[slot.name] = (item_frequencies[slot.name] or 0) + 1 | |
145 | displayManifest(manifest, mon_width, mon_height, backpacks_processed, i, size) | |
146 | local items = backpack.pushItems(peripheral.getName(chest), i, 64) | |
147 | if items ~= slot.count then | |
148 | addLog(items .. " of " .. slot.count .. " transferred, chest full? Failing.") | |
149 | return false, items_imported | |
150 | end | |
151 | items_imported = items_imported + 1 -- Count each stack as an "item" | |
152 | end | |
153 | end | |
154 | return true, items_imported | |
155 | end | |
156 | ||
157 | -- Empty turtle inventory | |
158 | local function emptyTurtleInventory() | |
159 | local success = true | |
160 | local mon_width, mon_height = initMonitor(1.4) -- Scale 1.4 for dumping | |
161 | monitor.clear() | |
162 | monitor.setCursorPos(1, 1) | |
163 | monitor.write("Version " .. VERSION) | |
164 | monitor.setCursorPos(1, 2) | |
165 | monitor.write("Dumping empty backpacks...") | |
166 | turtle.turnRight() | |
167 | local ok, err = pcall(function() | |
168 | for slot = 1, BACKPACKS_PER_CYCLE do | |
169 | turtle.select(slot) | |
170 | if not turtle.drop() then | |
171 | addLog("Drop failed, likely had <" .. BACKPACKS_PER_CYCLE .. " backpacks.") | |
172 | success = false | |
173 | return | |
174 | end | |
175 | end | |
176 | end) | |
177 | turtle.turnLeft() -- Always turn back left | |
178 | if not ok then | |
179 | addLog("Error during dump: " .. tostring(err)) | |
180 | success = false | |
181 | end | |
182 | return success | |
183 | end | |
184 | ||
185 | -- Helper function to sort items for display | |
186 | local function sortItemsForDisplay(item_table, sort_by_value, ascending) | |
187 | local sorted_items = {} | |
188 | for name, value in pairs(item_table) do | |
189 | table.insert(sorted_items, {name = name, value = value}) | |
190 | end | |
191 | table.sort(sorted_items, function(a, b) | |
192 | if a.value == b.value then | |
193 | return a.name < b.name -- Alphabetical order for ties | |
194 | end | |
195 | if ascending then | |
196 | return a.value < b.value | |
197 | else | |
198 | return a.value > b.value | |
199 | end | |
200 | end) | |
201 | return sorted_items | |
202 | end | |
203 | ||
204 | -- Display waiting screen with top looted and rarest items | |
205 | local function displayWaitingScreen(mon_width, mon_height, total_backpacks_processed, total_items_imported) | |
206 | monitor.clear() | |
207 | monitor.setCursorPos(1, 1) | |
208 | monitor.write("Version " .. VERSION) | |
209 | monitor.setCursorPos(1, 2) | |
210 | monitor.write("Waiting for backpacks...") | |
211 | monitor.setCursorPos(1, 3) | |
212 | monitor.write("Backpacks Processed: " .. total_backpacks_processed) | |
213 | monitor.setCursorPos(1, 4) | |
214 | monitor.write("Items Imported: " .. total_items_imported) | |
215 | ||
216 | -- Get top 10 looted items (by quantity) | |
217 | -- I believe this passes a global reference that sort uses internally as a local | |
218 | ||
219 | local top_looted = sortItemsForDisplay(item_quantities, true, false) | |
220 | local mid_point = math.ceil(mon_width / 2) | |
221 | local max_item_length = mid_point - 5 -- Reserve space for ": XXXXX" (up to 5 digits) | |
222 | ||
223 | -- Display top 10 looted items in two columns | |
224 | ||
225 | monitor.setCursorPos(1, 7) | |
226 | monitor.write("Top 10 Looted Items") | |
227 | local row = 8 -- Start items on line 8 | |
228 | local col = 1 | |
229 | for i = 1, math.min(10, #top_looted) do | |
230 | if i == 6 then | |
231 | col = mid_point | |
232 | row = 8 -- Reset row for second column | |
233 | end | |
234 | local item = top_looted[i] | |
235 | local display_name = item.name | |
236 | if #display_name > max_item_length then | |
237 | display_name = string.sub(display_name, 1, max_item_length - 3) .. "..." | |
238 | end | |
239 | monitor.setCursorPos(col, row) | |
240 | monitor.write(display_name .. ": " .. item.value) | |
241 | row = row + 1 | |
242 | end | |
243 | ||
244 | -- Display 5 rarest items below with a blank line | |
245 | local rarest_items = sortItemsForDisplay(item_frequencies, true, true) | |
246 | monitor.setCursorPos(1, 14) -- Moved to line 14 with a blank line on 13 | |
247 | monitor.write("5 Rarest Items Seen") | |
248 | row = 15 -- Start items on line 15 | |
249 | col = 1 | |
250 | for i = 1, math.min(5, #rarest_items) do | |
251 | local item = rarest_items[i] | |
252 | local display_name = item.name | |
253 | if #display_name > max_item_length then | |
254 | display_name = string.sub(display_name, 1, max_item_length - 3) .. "..." | |
255 | end | |
256 | monitor.setCursorPos(col, row) | |
257 | monitor.write(display_name .. ": " .. item.value) | |
258 | row = row + 1 | |
259 | end | |
260 | end | |
261 | ||
262 | -- Main function | |
263 | local function main() | |
264 | local mon_width, mon_height = initMonitor(1.4) -- Use 1.4 scale for waiting screen | |
265 | local total_backpacks_processed = 0 -- Track total backpacks processed | |
266 | local total_items_imported = 0 -- Track total items imported | |
267 | while true do | |
268 | -- Wait for redstone signal to indicate 9 backpacks (signal strength 15) | |
269 | displayWaitingScreen(mon_width, mon_height, total_backpacks_processed, total_items_imported) | |
270 | while redstone.getAnalogInput("bottom") ~= 15 do | |
271 | sleep(1) -- Check every second to avoid excessive CPU usage | |
272 | monitor.setCursorPos(1, 5) | |
273 | monitor.write("Signal: " .. redstone.getAnalogInput("bottom")) | |
274 | sleep(1) -- Update every second | |
275 | displayWaitingScreen(mon_width, mon_height, total_backpacks_processed, total_items_imported) | |
276 | end | |
277 | ||
278 | -- Process exactly 9 backpacks | |
279 | local backpacks_processed = 0 -- Track backpacks processed in this cycle | |
280 | local cycle_items_imported = 0 -- Track items imported in this cycle | |
281 | for slot = 1, BACKPACKS_PER_CYCLE do | |
282 | local manifest = {} -- Reset manifest for each backpack | |
283 | turtle.select(slot) | |
284 | redstone.setOutput("left", true) | |
285 | if not checkForBackpack() then | |
286 | redstone.setOutput("left", false) -- Reset redstone on abort | |
287 | addLog("Fewer than 9 backpacks available, proceeding with " .. backpacks_processed .. " backpacks.") | |
288 | break | |
289 | end | |
290 | redstone.setOutput("left", false) | |
291 | backpacks_processed = backpacks_processed + 1 | |
292 | ||
293 | local backpack = peripheral.wrap(backpack_side) | |
294 | local success, items_imported = processBackpack(backpack, chest, manifest, mon_width, mon_height, backpacks_processed) | |
295 | if not success then | |
296 | redstone.setOutput("left", false) -- Ensure redstone is off | |
297 | addLog("Chest full, proceeding to dump backpacks.") | |
298 | break | |
299 | end | |
300 | cycle_items_imported = cycle_items_imported + items_imported | |
301 | turtle.dig() | |
302 | sleep(1) -- Brief pause to show the manifest | |
303 | end | |
304 | ||
305 | -- Dump backpacks if any were processed | |
306 | if backpacks_processed > 0 then | |
307 | if not emptyTurtleInventory() then | |
308 | redstone.setOutput("left", false) -- Ensure redstone is off | |
309 | addLog("Dump failed, continuing to wait for next cycle.") | |
310 | -- Continue to waiting state instead of exiting | |
311 | end | |
312 | total_backpacks_processed = total_backpacks_processed + backpacks_processed | |
313 | total_items_imported = total_items_imported + cycle_items_imported | |
314 | end | |
315 | ||
316 | -- Return to waiting state | |
317 | mon_width, mon_height = initMonitor(1.4) -- Reset to 1.4 scale for waiting | |
318 | monitor.clear() | |
319 | monitor.setCursorPos(1, 1) | |
320 | monitor.write("Version " .. VERSION) | |
321 | monitor.setCursorPos(1, 2) | |
322 | monitor.write("Ready for next set of backpacks...") | |
323 | monitor.setCursorPos(1, 3) | |
324 | monitor.write("Backpacks Processed: " .. total_backpacks_processed) | |
325 | monitor.setCursorPos(1, 4) | |
326 | monitor.write("Items Imported: " .. total_items_imported) | |
327 | sleep(2) -- Brief pause before returning to waiting state | |
328 | filefunctions.saveStats("stats.dat") -- trying _G. out ,item_quantities,item_frequencies) | |
329 | print("Logging stats...") | |
330 | for k,v in pairs(item_quantities) do | |
331 | addLog("Qty", k, v) | |
332 | end | |
333 | for k,v in pairs(item_frequencies) do | |
334 | addLog("Freq", k, v) | |
335 | end | |
336 | addLog("Stats saved.") | |
337 | end | |
338 | end | |
339 | ||
340 | -- initialize log | |
341 | if fs.exists("log.txt") then | |
342 | else | |
343 | file = io.open("logs.txt", "w") | |
344 | file:write("") | |
345 | file:close() | |
346 | end | |
347 | ||
348 | -- load previous values (if any) | |
349 | -- item_quantities,item_frequencies= | |
350 | filefunctions.loadStats("stats.dat") -- _G. should be global so trying to revert back to not passing the tables | |
351 | -- for whatever reason my non local item_* variables seem to act local in scope and seems to need the following to work | |
352 | item_quantities=_G.item_quantities | |
353 | item_frequencies=_G.item_frequencies | |
354 | ||
355 | print("checking stats...") | |
356 | for k,v in pairs(item_quantities) do | |
357 | print("Qty", k, v) | |
358 | end | |
359 | for k,v in pairs(item_frequencies) do | |
360 | print("Freq", k, v) | |
361 | end | |
362 | ||
363 | -- Run the program | |
364 | main() | |
365 | -- Ensure redstone is off on program exit | |
366 | redstone.setOutput("left", false) | |
367 | ||
368 | -- Mobs drop backpacks now. Basic setup is AE2 export bus->ender chest, that 'combo' ender chest exports to a placer. | |
369 | -- The redstone signal on the left is letting a placer place the backpack (back-filled from an item | |
370 | -- pipe from the ender chest full of backpacks from the AE2) it's carried along the top of a block via redstone wire | |
371 | -- the chest referenced in code below is where the turtle dumps the inventory inside the backpacks | |
372 | -- finally after 16 backpacks it turns to the right and drops them all in a different ender chest that then drops them | |
373 | -- into a matter condenser for singularities. | |
374 | -- I've introduced a comparator and a couple ARS redstone relays linked one in front of the comparator, and one below the turtle. | |
375 | -- Alternative implementation by DesatKorun at https://pastebin.com/gkDnN8Fm |