Add composting functionality and defragmentation process to inventory management

This commit is contained in:
MayaTheShy
2026-03-15 23:17:29 -04:00
parent a17b9c64d8
commit e1dc56921f

View File

@@ -8,6 +8,9 @@ local MONITOR_SIDE = "left"
local SCAN_INTERVAL = 120 -- seconds between full background scans
local SMELT_INTERVAL = 3 -- seconds between furnace checks
local SMELT_RESERVE = 64 -- keep at least 1 stack of each raw material
local DEFRAG_INTERVAL = 60 -- seconds between defrag passes
local COMPOST_INTERVAL = 10 -- seconds between composter checks
local ALERT_INTERVAL = 15 -- seconds between alert re-checks
local CACHE_FILE = ".inventory_cache" -- persistent cache file
local SMELTER_MONITOR_SIDE = "top"
local DISABLED_RECIPES_FILE = ".disabled_recipes"
@@ -207,6 +210,155 @@ local FUEL_LIST = {
local FUEL_SET = {}
for _, f in ipairs(FUEL_LIST) do FUEL_SET[f.name] = true end
-------------------------------------------------
-- Compostable items (pushed into composters)
-- Each item has a chance to raise the compost level:
-- 1.0 = always raises level, 0.3 = 30% chance etc.
-- CC:Tweaked composters accept pushItems; the
-- composter itself decides whether the item counts.
-------------------------------------------------
local COMPOSTABLE = {
-- Seeds & crops
"minecraft:wheat_seeds",
"minecraft:beetroot_seeds",
"minecraft:pumpkin_seeds",
"minecraft:melon_seeds",
"minecraft:torchflower_seeds",
"minecraft:pitcher_pod",
"minecraft:wheat",
"minecraft:beetroot",
"minecraft:carrot",
"minecraft:melon_slice",
"minecraft:pumpkin",
"minecraft:carved_pumpkin",
"minecraft:sweet_berries",
"minecraft:glow_berries",
-- Plant blocks
"minecraft:tall_grass",
"minecraft:short_grass",
"minecraft:fern",
"minecraft:large_fern",
"minecraft:dead_bush",
"minecraft:vine",
"minecraft:hanging_roots",
"minecraft:small_dripleaf",
"minecraft:big_dripleaf",
"minecraft:moss_block",
"minecraft:moss_carpet",
"minecraft:azalea",
"minecraft:flowering_azalea",
"minecraft:spore_blossom",
"minecraft:seagrass",
"minecraft:sea_pickle",
"minecraft:lily_pad",
"minecraft:sugar_cane",
"minecraft:kelp",
"minecraft:dried_kelp",
"minecraft:cactus",
"minecraft:bamboo",
"minecraft:nether_wart",
"minecraft:crimson_fungus",
"minecraft:warped_fungus",
"minecraft:crimson_roots",
"minecraft:warped_roots",
"minecraft:shroomlight",
"minecraft:weeping_vines",
"minecraft:twisting_vines",
-- Leaves
"minecraft:oak_leaves",
"minecraft:spruce_leaves",
"minecraft:birch_leaves",
"minecraft:jungle_leaves",
"minecraft:acacia_leaves",
"minecraft:dark_oak_leaves",
"minecraft:mangrove_leaves",
"minecraft:cherry_leaves",
"minecraft:azalea_leaves",
"minecraft:flowering_azalea_leaves",
-- Flowers
"minecraft:dandelion",
"minecraft:poppy",
"minecraft:blue_orchid",
"minecraft:allium",
"minecraft:azure_bluet",
"minecraft:red_tulip",
"minecraft:orange_tulip",
"minecraft:white_tulip",
"minecraft:pink_tulip",
"minecraft:oxeye_daisy",
"minecraft:cornflower",
"minecraft:lily_of_the_valley",
"minecraft:sunflower",
"minecraft:lilac",
"minecraft:rose_bush",
"minecraft:peony",
"minecraft:wither_rose",
"minecraft:torchflower",
"minecraft:pitcher_plant",
-- Saplings
"minecraft:oak_sapling",
"minecraft:spruce_sapling",
"minecraft:birch_sapling",
"minecraft:jungle_sapling",
"minecraft:acacia_sapling",
"minecraft:dark_oak_sapling",
"minecraft:mangrove_propagule",
"minecraft:cherry_sapling",
-- Food waste
"minecraft:rotten_flesh",
"minecraft:spider_eye",
"minecraft:poisonous_potato",
"minecraft:fermented_spider_eye",
"minecraft:apple",
"minecraft:bread",
"minecraft:cookie",
"minecraft:cake",
"minecraft:pumpkin_pie",
-- Farmer's Delight compostables
"farmersdelight:tree_bark",
"farmersdelight:straw",
"farmersdelight:canvas",
"farmersdelight:rice",
"farmersdelight:rice_panicle",
"farmersdelight:onion",
"farmersdelight:tomato",
"farmersdelight:cabbage",
"farmersdelight:cabbage_leaf",
}
-- Build set for quick lookup
local COMPOSTABLE_SET = {}
for _, name in ipairs(COMPOSTABLE) do COMPOSTABLE_SET[name] = true end
-- Reserve: keep at least this many of each compostable before composting the rest
local COMPOST_RESERVE = 16
-------------------------------------------------
-- Low-stock alerts
-- When a tracked item drops below 'min', an alert
-- is shown on the inventory monitor.
-------------------------------------------------
local LOW_STOCK_ALERTS = {
{ name = "minecraft:coal", min = 64, label = "Coal" },
{ name = "minecraft:charcoal", min = 64, label = "Charcoal" },
{ name = "minecraft:torch", min = 64, label = "Torches" },
{ name = "minecraft:arrow", min = 64, label = "Arrows" },
{ name = "minecraft:cooked_beef", min = 32, label = "Steak" },
{ name = "minecraft:cooked_porkchop",min = 32, label = "Porkchops" },
{ name = "minecraft:bread", min = 32, label = "Bread" },
{ name = "minecraft:iron_ingot", min = 64, label = "Iron" },
{ name = "minecraft:gold_ingot", min = 32, label = "Gold" },
{ name = "minecraft:diamond", min = 16, label = "Diamond" },
{ name = "minecraft:bone_meal", min = 32, label = "Bone Meal" },
{ name = "minecraft:oak_planks", min = 64, label = "Planks" },
{ name = "minecraft:cobblestone", min = 128, label = "Cobblestone" },
}
-- Active alerts (populated by checkAlerts)
local activeAlerts = {}
-------------------------------------------------
-- Cached data (updated by background scanner)
-------------------------------------------------
@@ -235,6 +387,8 @@ local activity = {
dispensing = false, -- order in progress
scanning = false, -- background scan in progress
smelting = false, -- auto-smelt in progress
defragging = false, -- defrag in progress
composting = false, -- auto-compost in progress
}
-------------------------------------------------
@@ -731,6 +885,8 @@ local function drawDashboard()
if activity.dispensing then table.insert(actParts, "DISPENSING") end
if activity.smelting then table.insert(actParts, "SMELTING") end
if activity.scanning then table.insert(actParts, "SCANNING") end
if activity.defragging then table.insert(actParts, "DEFRAG") end
if activity.composting then table.insert(actParts, "COMPOST") end
monWrite(2, 2, table.concat(statusParts, " | "), colors.white, colors.gray)
@@ -956,7 +1112,13 @@ local function drawDashboard()
else
-- ===== Status message (h-2) =====
monFill(h - 2, colors.black)
if statusTimer > 0 and #statusMessage > 0 then
if #activeAlerts > 0 then
-- Show low-stock alerts scrolling through them
local alertIdx = math.floor(os.epoch("utc") / 2000) % #activeAlerts + 1
local a = activeAlerts[alertIdx]
local alertMsg = string.format(" LOW STOCK: %s (%d/%d) ", a.label, a.current, a.min)
monCenter(h - 2, alertMsg, colors.white, colors.red)
elseif statusTimer > 0 and #statusMessage > 0 then
monCenter(h - 2, statusMessage, statusColor, colors.black)
end
@@ -983,6 +1145,10 @@ local function drawDashboard()
bottomMsg = " SMELTING... "
elseif activity.sorting then
bottomMsg = " SORTING BARREL... "
elseif activity.defragging then
bottomMsg = " DEFRAGMENTING... "
elseif activity.composting then
bottomMsg = " COMPOSTING... "
end
monCenter(h, bottomMsg, colors.lightBlue, colors.blue)
end
@@ -1550,6 +1716,207 @@ local function autoSmelt()
return didWork
end
-------------------------------------------------
-- Defrag (consolidate partial stacks)
-------------------------------------------------
local function defragInventory()
local chests = getChests()
if #chests == 0 then return end
activity.defragging = true
needsRedraw = true
-- Build a map: itemName -> list of { chest, slot, count, maxCount }
local itemSlots = {}
for _, chestName in ipairs(chests) do
local inv = peripheral.wrap(chestName)
if inv then
local contents = inv.list()
local detail_cache = {}
for slot, item in pairs(contents) do
if not itemSlots[item.name] then
itemSlots[item.name] = {}
end
-- CC:Tweaked: getItemDetail gives maxCount (stack size)
local maxCount = 64
local ok, detail = pcall(inv.getItemDetail, slot)
if ok and detail and detail.maxCount then
maxCount = detail.maxCount
end
table.insert(itemSlots[item.name], {
chest = chestName,
slot = slot,
count = item.count,
max = maxCount,
})
end
end
end
-- For each item, try to merge partial stacks
local totalMerged = 0
for itemName, slots in pairs(itemSlots) do
-- Sort: smallest stacks first (donors), fullest last (receivers)
table.sort(slots, function(a, b) return a.count < b.count end)
local i = 1 -- donor (smallest)
local j = #slots -- receiver (largest, has room)
while i < j do
local donor = slots[i]
local recv = slots[j]
-- Skip if same slot
if donor.chest == recv.chest and donor.slot == recv.slot then
i = i + 1
elseif donor.count == 0 then
i = i + 1
elseif recv.count >= recv.max then
j = j - 1
else
local space = recv.max - recv.count
local toMove = math.min(donor.count, space)
local donorInv = peripheral.wrap(donor.chest)
if donorInv then
local n = donorInv.pushItems(recv.chest, donor.slot, toMove, recv.slot)
if n and n > 0 then
donor.count = donor.count - n
recv.count = recv.count + n
totalMerged = totalMerged + n
end
end
if donor.count <= 0 then i = i + 1 end
if recv.count >= recv.max then j = j - 1 end
end
end
end
if totalMerged > 0 then
print(string.format("[DEFRAG] Consolidated %d items", totalMerged))
end
activity.defragging = false
needsRedraw = true
end
-------------------------------------------------
-- Auto-compost
-------------------------------------------------
local function getComposters()
local composters = {}
for _, name in ipairs(peripheral.getNames()) do
if peripheral.getType(name) == "minecraft:composter" then
table.insert(composters, name)
end
end
return composters
end
local function autoCompost()
local composters = getComposters()
if #composters == 0 then return end
local chests = getChests()
local catalogue = cache.catalogue
local didWork = false
for _, cname in ipairs(composters) do
local composter = peripheral.wrap(cname)
if composter then
-- 1) Pull output (bone meal, slot 1 when full) back to chests
local contents = composter.list()
if contents then
for slot, item in pairs(contents) do
if item.name == "minecraft:bone_meal" then
for _, chest in ipairs(chests) do
local n = composter.pushItems(chest, slot)
if n and n > 0 then
adjustCache("minecraft:bone_meal", chest, n)
print(string.format("[COMPOST] Bone meal x%d -> %s", n, chest))
didWork = true
break
end
end
end
end
end
-- 2) Feed compostable items (push 1 at a time — composter has 1 slot)
contents = composter.list()
local hasInput = contents and next(contents)
if not hasInput then
for _, itemName in ipairs(COMPOSTABLE) do
if catalogue[itemName] then
-- Count total in storage
local totalInStorage = 0
for _, src in ipairs(catalogue[itemName]) do
totalInStorage = totalInStorage + src.total
end
local available = totalInStorage - COMPOST_RESERVE
if available > 0 then
-- Push 1 item at a time to the composter
local fed = false
for _, source in ipairs(catalogue[itemName]) do
local chest = peripheral.wrap(source.chest)
if chest then
for slot, slotItem in pairs(chest.list()) do
if slotItem.name == itemName then
local n = chest.pushItems(cname, slot, 1)
if n and n > 0 then
adjustCache(itemName, source.chest, -n)
print(string.format("[COMPOST] Fed %s x%d -> %s",
itemName, n, cname))
didWork = true
fed = true
break
end
end
end
end
if fed then break end
end
if fed then break end -- composter now has item, move to next composter
end
end
end
end
end
end
return didWork
end
-------------------------------------------------
-- Low-stock alert checker
-------------------------------------------------
local function checkAlerts()
local alerts = {}
for _, alert in ipairs(LOW_STOCK_ALERTS) do
local total = 0
if cache.catalogue[alert.name] then
for _, src in ipairs(cache.catalogue[alert.name]) do
total = total + src.total
end
end
if total < alert.min then
table.insert(alerts, {
label = alert.label,
current = total,
min = alert.min,
})
end
end
activeAlerts = alerts
if #alerts > 0 then
needsRedraw = true
smelterNeedsRedraw = true
end
end
-------------------------------------------------
-- Order
-------------------------------------------------
@@ -1813,6 +2180,16 @@ local function main()
end
print(string.format("[INIT] %d/%d recipes enabled", enabledCount, totalRecipeCount))
-- Detect composters
local composters = getComposters()
if #composters > 0 then
print(string.format("[OK] Composters: %d", #composters))
else
print("[INFO] No composters on network")
end
print(string.format("[INIT] Tracking %d low-stock alerts", #LOW_STOCK_ALERTS))
print("")
print("Console shows log. Use the monitors to interact.")
print("")
@@ -1899,6 +2276,7 @@ local function main()
-- If we loaded from disk cache, refresh immediately in background
if cacheLoaded then
pcall(refreshCache)
pcall(checkAlerts)
needsRedraw = true
smelterNeedsRedraw = true
print("[INIT] Background refresh complete. " .. #cache.itemList .. " types.")
@@ -1906,6 +2284,7 @@ local function main()
while true do
sleep(SCAN_INTERVAL)
pcall(refreshCache)
pcall(checkAlerts)
needsRedraw = true
smelterNeedsRedraw = true
end
@@ -1935,7 +2314,45 @@ local function main()
end
end,
-- Task 4: Inventory dashboard redraw (event-driven, checks every 0.1s)
-- Task 4: Defrag (consolidate partial stacks)
function()
sleep(10) -- initial delay to let first scan finish
while true do
activity.defragging = true
needsRedraw = true
pcall(defragInventory)
activity.defragging = false
needsRedraw = true
sleep(DEFRAG_INTERVAL)
end
end,
-- Task 5: Auto-compost
function()
while true do
activity.composting = true
needsRedraw = true
pcall(autoCompost)
activity.composting = false
needsRedraw = true
pcall(checkAlerts)
sleep(COMPOST_INTERVAL)
end
end,
-- Task 6: Low-stock alert checker
function()
sleep(5) -- initial delay
pcall(checkAlerts)
needsRedraw = true
while true do
sleep(ALERT_INTERVAL)
pcall(checkAlerts)
needsRedraw = true
end
end,
-- Task 7: Inventory dashboard redraw (event-driven, checks every 0.1s)
function()
needsRedraw = true
while true do
@@ -1951,11 +2368,15 @@ local function main()
needsRedraw = true
end
end
-- Redraw periodically for alert cycling
if #activeAlerts > 0 then
needsRedraw = true
end
sleep(0.1)
end
end,
-- Task 5: Smelter dashboard redraw
-- Task 8: Smelter dashboard redraw
function()
smelterNeedsRedraw = true
while true do
@@ -1967,7 +2388,7 @@ local function main()
end
end,
-- Task 6: Touch event listener (both monitors)
-- Task 9: Touch event listener (both monitors)
function()
while true do
local event, side, x, y = os.pullEvent("monitor_touch")