hornedcommando

Minecraft ComputerCraft Modem PressManager

Nov 21st, 2025
51
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.91 KB | Gaming | 0 0
  1. --
  2. -- Desc: A press manager program for FTB StoneBlock 4
  3. -- Indexes primary inventory and attempts to balance press inputs/outputs
  4. -- simplifies setup because modem can connect directly to press inventory
  5. -- outputs are returned to chests that already contain them by default
  6. -- IMPORTANT!: as you connect modems to presses/chests, update the config to match
  7. -- their names, or this will not work correctly.
  8. --
  9.  
  10. -- By: hornedcommando
  11.  
  12. -- =========================
  13. -- ========== CONFIG =======
  14. -- =========================
  15. local CONFIG = {
  16.   -- Your press peripheral name
  17.   press = "create:depot_0",
  18.  
  19.   -- by default inputs where their outputs > 128 are trashed here, leave as nil to disable
  20.   trashPeripheral = "trashcans:item_trash_can_tile_1",
  21.  
  22.   -- Optional: a fallback output chest if no merge target exists
  23.   fallbackOutputChest = "ironchest:crystal_chest_0",
  24.  
  25.   -- Inputs and their outputs (used for balance comparisons).
  26.   -- change this if your modpack has different press recipes
  27.   inputs = {
  28.     ["ftbmaterials:zinc_ingot"] = {
  29.       outputs = {
  30.         "ftbmaterials:zinc_plate"
  31.       }
  32.     },
  33.     ["minecraft:gold_ingot"] = {
  34.       outputs = {
  35.         "ftbmaterials:gold_plate"
  36.       }
  37.     },
  38.     ["minecraft:iron_ingot"] = {
  39.       outputs = {
  40.         "ftbmaterials:iron_plate"
  41.       }
  42.     },
  43.     ["ftbmaterials:aluminum_ingot"] = {
  44.       outputs = {
  45.         "ftbmaterials:aluminum_plate"
  46.       }
  47.     },
  48.     ["minecraft:copper_ingot"] = {
  49.       outputs = {
  50.         "ftbmaterials:copper_plate"
  51.       }
  52.     },
  53.  
  54.     ["ftbmaterials:lead_ingot"] = {
  55.       outputs = {
  56.         "ftbmaterials:lead_plate"
  57.       }
  58.     },
  59.    
  60.     ["ftbmaterials:nickel_ingot"] = {
  61.       outputs = {
  62.         "ftbmaterials:nickel_plate"
  63.       }
  64.     },
  65.  
  66.     ["ftbmaterials:steel_ingot"] = {
  67.       outputs = {
  68.         "ftbmaterials:steel_plate"
  69.       }
  70.     },
  71.  
  72.     ["ftbmaterials:electrum_ingot"] = {
  73.       outputs = {
  74.         "ftbmaterials:electrum_plate"
  75.       }
  76.     },
  77.  
  78.     ["ftbmaterials:bronze_ingot"] = {
  79.       outputs = {
  80.         "ftbmaterials:bronze_plate"
  81.       }
  82.     },
  83.  
  84.     ["minecraft:sugar_cane"] = {
  85.       outputs = {
  86.         "minecraft:paper"
  87.       }
  88.     },
  89.  
  90.     ["create:pulp"] = {
  91.       outputs = {
  92.         "create:cardboard "
  93.       }
  94.     },
  95.  
  96.     ["ftbmaterials:brass_ingot"] = {
  97.         outputs = {
  98.           "ftbmaterials:brass_plate"
  99.         }
  100.       },
  101.  
  102.     ["create_enchantment_industry:super_experience_block"] = {
  103.       outputs = {
  104.         "create_enchantment_industry:super_enchanting_template"
  105.       }
  106.     },
  107.  
  108.     ["ftbmaterials:constantan_ingot"] = {
  109.       outputs = {
  110.         "ftbmaterials:constantan_plate"
  111.       }
  112.     },
  113.  
  114.     ["create:experience_block"] = {
  115.       outputs = {
  116.         "create_enchantment_industry:enchanting_template"
  117.       }
  118.     },
  119.  
  120.     ["ftbmaterials:invar_ingot"] = {
  121.       outputs = {
  122.         "ftbmaterials:invar_plate"
  123.       }
  124.     },
  125.   },
  126.  
  127.   -- Balancer thresholds
  128.   scarceLT = 9,      -- <9 scarce
  129.   availLE = 64,      -- 9..64 available
  130.   abundLE = 128,     -- 64..128 abundant
  131.   reserveCount = 8,  -- keep at least this many of the INPUT unprocessed in Case #2
  132.  
  133.   -- Push/pull parameters
  134.   maxPushPerCycle = 9,         -- conservative push to prevent depleting scarce resources
  135.   maxOutputPullPerCycle = 512, -- cap pulls per loop to avoid huge bursts
  136.  
  137.   -- Sleep timings
  138.   sleepShort = 2,   -- after any action
  139.   sleepIdle = 5,    -- when nothing to do
  140.   sleepError = 5,   -- on warnings
  141.  
  142.   -- Logging
  143.   verbose = true,
  144. }
  145.  
  146. -- =========================
  147. -- ====== UTILITIES ========
  148. -- =========================
  149. local function log(...)
  150.   if CONFIG.verbose then
  151.     local t = {}
  152.     for i = 1, select("#", ...) do t[#t+1] = tostring(select(i, ...)) end
  153.     print(table.concat(t))
  154.   end
  155. end
  156.  
  157. local function isInv(name)
  158.   if not peripheral.isPresent(name) then return false end
  159.   local m = peripheral.getMethods(name) or {}
  160.   local has = {}
  161.   for _,x in ipairs(m) do has[x] = true end
  162.   return has.list and (has.pushItems or has.pullItems)
  163. end
  164.  
  165. local function allInventories()
  166.   local out = {}
  167.   for _,n in ipairs(peripheral.getNames()) do
  168.     if isInv(n) then table.insert(out, n) end
  169.   end
  170.   table.sort(out)
  171.   return out
  172. end
  173.  
  174. local function list(name)
  175.   local ok, res = pcall(function() return peripheral.call(name, "list") end)
  176.   if ok and type(res) == "table" then return res end
  177.   return {}
  178. end
  179. local function getItemDetail(name, slot)
  180.   local ok, d = pcall(function() return peripheral.call(name, "getItemDetail", slot) end)
  181.   if ok then return d end
  182.   return nil
  183. end
  184.  
  185. local function maxCountFor(name, slot)
  186.   local d = getItemDetail(name, slot)
  187.   if d and d.maxCount then return d.maxCount end
  188.   return 64
  189. end
  190.  
  191. local function countInInv(name, id)
  192.   local total = 0
  193.   for _, it in pairs(list(name)) do
  194.     if it.name == id then total = total + (it.count or 0) end
  195.   end
  196.   return total
  197. end
  198.  
  199. local function countAcrossNetwork(id, invs)
  200.   local total = 0
  201.   for _, n in ipairs(invs) do
  202.     total = total + countInInv(n, id)
  203.   end
  204.   return total
  205. end
  206. local function stacksAcrossNetwork(id, invs, exclude)
  207.   local out = {}
  208.   for _, n in ipairs(invs) do
  209.     if n ~= exclude then
  210.       for slot, it in pairs(list(n)) do
  211.         if it.name == id and (it.count or 0) > 0 then
  212.           table.insert(out, { periph = n, slot = slot, count = it.count })
  213.         end
  214.       end
  215.     end
  216.   end
  217.   return out
  218. end
  219.  
  220. local function invFreeSlots(name)
  221.   local okS, size = pcall(function() return peripheral.call(name, "size") end)
  222.   if not okS or type(size) ~= "number" then return 0 end
  223.   local used = 0
  224.   for _ in pairs(list(name)) do used = used + 1 end
  225.   return math.max(0, size - used)
  226. end
  227.  
  228. local function canReach(src, dst)
  229.   local ok, err = pcall(function() peripheral.call(src, "pushItems", dst, 1, 0) end)
  230.   if ok then return true end
  231.   local msg = tostring(err or "")
  232.   if msg:find("does not exist") or msg:find("No such peripheral") then return false end
  233.   return true
  234. end
  235.  
  236. -- Prefer an inventory that already has the item, else fallback chest, else max free slots
  237. local function chooseOutputTarget(itemId, invs, press, fallback)
  238.   local best, bestCount = nil, -1
  239.   for _, n in ipairs(invs) do
  240.     if n ~= press then
  241.       local c = countInInv(n, itemId)
  242.       if c > 0 and canReach(press, n) then
  243.         if c > bestCount then best, bestCount = n, c end
  244.       end
  245.     end
  246.   end
  247.   if best then return best end
  248.   if fallback and fallback ~= press and canReach(press, fallback) then return fallback end
  249.   -- else pick most free slots
  250.   local mfName, mf = nil, -1
  251.   for _, n in ipairs(invs) do
  252.     if n ~= press and canReach(press, n) then
  253.       local free = invFreeSlots(n)
  254.       if free > mf then mfName, mf = n, free end
  255.     end
  256.   end
  257.   return mfName
  258. end
  259.  
  260. local function classify(amount)
  261.   if amount < CONFIG.scarceLT then return "scarce"
  262.   elseif amount <= CONFIG.availLE then return "available"
  263.   elseif amount <= CONFIG.abundLE then return "abundant"
  264.   else return "trash" end
  265. end
  266.  
  267. local function sumCounts(ids, invs)
  268.   local t = 0
  269.   for _, id in ipairs(ids) do
  270.     t = t + countAcrossNetwork(id, invs)
  271.   end
  272.   return t
  273. end
  274.  
  275. local function outputsStatus(outputs, invs, classifyFn)
  276.   local per = {}
  277.   local allAbundant = true
  278.   local anyBelowAbundant = false
  279.   local anyScarce = false
  280.  
  281.   for _, oid in ipairs(outputs or {}) do
  282.     local c = countAcrossNetwork(oid, invs)
  283.     local b = classifyFn(c) -- uses CONFIG thresholds
  284.     per[oid] = { count = c, band = b }
  285.  
  286.     if b == "scarce" or b == "available" then
  287.       anyBelowAbundant = true
  288.     end
  289.     if b == "scarce" then
  290.       anyScarce = true
  291.     end
  292.     if b ~= "abundant" and b ~= "trash" then
  293.       allAbundant = false
  294.     end
  295.   end
  296.  
  297.   -- If there are no outputs mapped, treat as "unknown" => do not block processing
  298.   if not outputs or #outputs == 0 then
  299.     allAbundant = false
  300.     anyBelowAbundant = true
  301.   end
  302.  
  303.   return per, allAbundant, anyBelowAbundant, anyScarce
  304. end
  305.  
  306. -- =========================
  307. -- ===== CORE ACTIONS ======
  308. -- =========================
  309. local function pushToPress(fromStacks, press, maxToPush)
  310.   local remain = maxToPush
  311.   local movedTotal = 0
  312.   for _, st in ipairs(fromStacks) do
  313.     if remain <= 0 then break end
  314.     local move = math.min(remain, st.count)
  315.     local ok, movedOrErr = pcall(function()
  316.       -- No toSlot: let the press route to valid input
  317.       return peripheral.call(st.periph, "pushItems", press, st.slot, move)
  318.     end)
  319.     if ok and type(movedOrErr) == "number" and movedOrErr > 0 then
  320.       remain = remain - movedOrErr
  321.       movedTotal = movedTotal + movedOrErr
  322.       log("[PRESS] Pushed ", movedOrErr, " from ", st.periph, " -> ", press)
  323.     else
  324.       log("[WARN] push to press failed from ", st.periph, ": ", tostring(movedOrErr))
  325.     end
  326.   end
  327.   return movedTotal
  328. end
  329.  
  330. local function pullOutputsFromPress(press, invs, inputsSet)
  331.   local lst = list(press)
  332.   local pulled = 0
  333.   local pulledItems = {}
  334.  
  335.   for slot, it in pairs(lst) do
  336.     if it and it.name then
  337.       local id = it.name
  338.       local isInput = inputsSet[id] == true
  339.  
  340.       if (not isInput) and it.count > 0 then
  341.         local dest = chooseOutputTarget(id, invs, press, CONFIG.fallbackOutputChest)
  342.         if dest then
  343.           local moved = peripheral.call(press, "pushItems", dest, slot, it.count)
  344.           if moved and moved > 0 then
  345.             pulled = pulled + moved
  346.             pulledItems[id] = (pulledItems[id] or 0) + moved
  347.             log("[ROUTE] ", id, " x", moved, " -> ", dest)
  348.           end
  349.         else
  350.           log("[WARN] No reachable output destination for ", id, " (holding in press for now)")
  351.         end
  352.       end
  353.     end
  354.   end
  355.  
  356.   return pulled, pulledItems
  357. end
  358.  
  359. local function disposeExcessInput(inputId, overAmount, invs, press, trash)
  360.   if not trash or not peripheral.isPresent(trash) then return 0 end
  361.   if overAmount <= 0 then return 0 end
  362.   local stacks = stacksAcrossNetwork(inputId, invs, press)
  363.   local remaining = overAmount
  364.   local disposed = 0
  365.   for _, st in ipairs(stacks) do
  366.     if remaining <= 0 then break end
  367.     local move = math.min(remaining, st.count)
  368.     local ok, movedOrErr = pcall(function()
  369.       return peripheral.call(st.periph, "pushItems", trash, st.slot, move)
  370.     end)
  371.     if ok and type(movedOrErr) == "number" and movedOrErr > 0 then
  372.       remaining = remaining - movedOrErr
  373.       disposed = disposed + movedOrErr
  374.       log("[TRASH] ", inputId, " x", movedOrErr, " from ", st.periph, " -> ", trash)
  375.     else
  376.       log("[WARN] Dispose failed from ", st.periph, " -> ", trash, ": ", tostring(movedOrErr))
  377.     end
  378.   end
  379.   return disposed
  380. end
  381.  
  382. -- =========================
  383. -- ====== MAIN LOOP ========
  384. -- =========================
  385. local function tick()
  386.   local press = CONFIG.press
  387.   if not peripheral.isPresent(press) then
  388.     log("[WARN] Press not present: ", press)
  389.     sleep(CONFIG.sleepError)
  390.     return
  391.   end
  392.  
  393.   local invs = allInventories()
  394.   if #invs == 0 then
  395.     log("[INFO] No inventories on network.")
  396.     sleep(CONFIG.sleepIdle)
  397.     return
  398.   end
  399.  
  400.   -- Build a set of input IDs for quick checks
  401.   local inputsSet, recipeOrder = {}, {}
  402.   for inputId, _ in pairs(CONFIG.inputs) do
  403.     inputsSet[inputId] = true
  404.     table.insert(recipeOrder, inputId)
  405.   end
  406.   table.sort(recipeOrder)
  407.  
  408.   local didSomething = false
  409.  
  410.   for _, inputId in ipairs(recipeOrder) do
  411.     local recipe = CONFIG.inputs[inputId]
  412.     local outputs = recipe.outputs or {}
  413.     local inputCount = countAcrossNetwork(inputId, invs)
  414.     if inputCount <= 0 then
  415.       log("[SKIP] No input found: ", inputId)
  416.       goto continue_input
  417.     end
  418.  
  419.     local inputBand = classify(inputCount)
  420.     local per, allAbundant, anyBelowAbundant = outputsStatus(outputs, invs, classify)
  421.  
  422.     -- Debug snapshot
  423.     do
  424.       local dbg = {}
  425.       for oid, st in pairs(per) do
  426.         table.insert(dbg, (oid .. "=" .. st.count .. "(" .. st.band .. ")"))
  427.       end
  428.       table.sort(dbg)
  429.       log(string.format("[EVAL] %s input=%d(%s) | outputs: %s",
  430.         inputId, inputCount, inputBand, table.concat(dbg, ", ")))
  431.     end
  432.  
  433.     -- Decision rules:
  434.  
  435.     if inputBand == "scarce" then
  436.       -- Rule: scarce inputs are preserved (don't process)
  437.       log("[HOLD] Scarce input, skipping: ", inputId)
  438.  
  439.     elseif inputBand == "available" then
  440.       -- Process only if ANY output is scarce (< 9), not if they're already available (9-64)
  441.       local per, allAbundant, anyBelowAbundant, anyScarce = outputsStatus(outputs, invs, classify)
  442.       if anyScarce then
  443.         local stacks = stacksAcrossNetwork(inputId, invs, press)
  444.         -- Reserve a floor of input; process only what's above reserve
  445.         local pushBudget = math.max(0, inputCount - CONFIG.reserveCount)
  446.         pushBudget = math.min(pushBudget, CONFIG.maxPushPerCycle)
  447.         if pushBudget > 0 then
  448.           local moved = pushToPress(stacks, press, pushBudget)
  449.           if moved > 0 then didSomething = true end
  450.         else
  451.           log("[OK] Reserve intact; nothing to push for ", inputId)
  452.         end
  453.       else
  454.         log("[OK] All outputs already at available level or higher; skipping processing for ", inputId)
  455.       end
  456.     elseif inputBand == "abundant" then
  457.       -- Process if ANY output is below abundant; otherwise skip
  458.       if anyBelowAbundant then
  459.         local stacks = stacksAcrossNetwork(inputId, invs, press)
  460.         local moved = pushToPress(stacks, press, CONFIG.maxPushPerCycle)
  461.         if moved > 0 then didSomething = true end
  462.       else
  463.         log("[OK] All outputs abundant; nothing to process for ", inputId)
  464.       end
  465.  
  466.     else -- inputBand == "trash" (> 128)
  467.       if anyBelowAbundant then
  468.         -- Still need outputs: process rather than dispose
  469.         local stacks = stacksAcrossNetwork(inputId, invs, press)
  470.         local moved = pushToPress(stacks, press, CONFIG.maxPushPerCycle)
  471.         if moved > 0 then didSomething = true end
  472.       else
  473.         -- All outputs abundant: safe to dispose input above 128 if trash is configured
  474.         if CONFIG.trashPeripheral then
  475.           local over = inputCount - CONFIG.abundLE
  476.           if over > 0 then
  477.             local disposed = disposeExcessInput(inputId, over, invs, press, CONFIG.trashPeripheral)
  478.             if disposed > 0 then didSomething = true end
  479.           else
  480.             log("[OK] No excess to dispose for ", inputId)
  481.           end
  482.         else
  483.           log("[OK] Would dispose excess of ", inputId, " but no trash configured")
  484.         end
  485.       end
  486.     end
  487.  
  488.     ::continue_input::
  489.   end
  490.  
  491.   -- After any pushes, pull outputs once to clear machine buffers
  492.   local pulled, pulledItems = pullOutputsFromPress(press, invs, inputsSet)
  493.   if pulled > 0 then
  494.     didSomething = true
  495.   end
  496.  
  497.   if didSomething then
  498.     sleep(CONFIG.sleepShort)
  499.   else
  500.     sleep(CONFIG.sleepIdle)
  501.   end
  502. end
  503.  
  504. local function main()
  505.   print("Press Manager starting...")
  506.   while true do
  507.     tick()
  508.   end
  509. end
  510.  
  511. main()
Add Comment
Please, Sign In to add comment