View difference between Paste ID: eDNsazPj and QYe4g6EP
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