Not a member of GistPad yet?
Sign Up,
it unlocks many cool features!
- -- RSWarehouse.lua
- -- Construction: https://cdn.domekologe.eu/gameserver/minecraft/cc/Minecolonies_RefinedStorage_Integration.png
- -- IMPORTANT: Entangled Block MUST be on left Side of Computer, if not, change direction variable!
- --[[
- Changelog:
- - Change Domum Items, that only items from architects cutter are excluded
- - [FIX] pcall wrapping for all peripheral calls to prevent crashes
- - [FIX] exportItem return value handling (can return nil on error)
- - [FIX] craftItem can return job object in newer AP, handle truthy check
- - [FIX] Variant comparison case-insensitive ("Unknown" vs "unknown")
- - [FIX] drawLine right-aligned text off-by-one positioning
- - [FIX] Cleanup loop: collect keys first, then delete (safe iteration)
- - [FIX] Partial delivery now clears stale crafting state
- - [FIX] Monitor overflow protection
- - [FIX] NBT serialization in makeRequestId protected with pcall
- - [FIX] Equipment requests are now fully delivered from RS (exact item match)
- - [NEW] Equipment (tools/armor) delivery: exports the exact requested item
- - [NEW] Multi-item fallback: tries all r.items[] alternatives for equipment
- - [NEW] isItemCrafting() check before starting duplicate craft jobs
- - [NEW] Sorted request display by status
- - [NEW] Error recovery: auto-reconnect peripherals on detach
- - [NEW] Statistics line on monitor
- - [NEW] Stale crafting job timeout (5 minutes)
- - [NEW] Peripheral type supports both "rs_bridge"/"rsBridge" naming
- - [NEW] 1.21.1 Data Components support alongside legacy NBT
- ]]--
- -------------------------------------------------------------------------------
- -- CONFIGURATION
- -------------------------------------------------------------------------------
- local time_between_runs = 15
- local MAX_EXPORT = 256
- local direction = "left"
- local CRAFT_TIMEOUT_MS = 5 * 60 * 1000 -- 5 min stale craft timeout
- -------------------------------------------------------------------------------
- -- INIT
- -------------------------------------------------------------------------------
- local monitor, bridge, colony
- local function findPeripherals()
- monitor = peripheral.find("monitor")
- if not monitor then error("Monitor not found") end
- monitor.setTextScale(0.5)
- -- AP 0.7+/0.8+ for 1.21.1 may register as "rsBridge" instead of "rs_bridge"
- bridge = peripheral.find("rsBridge") or peripheral.find("rs_bridge")
- if not bridge then error("RS Bridge not found") end
- -- Colony Integrator: "colonyIntegrator" or "colony_integrator"
- colony = peripheral.find("colonyIntegrator") or peripheral.find("colony_integrator")
- if not colony then error("Colony Integrator not found") end
- end
- findPeripherals()
- local deliveredRequests = {}
- local craftingRequests = {}
- local stats = { total = 0, delivered = 0, crafting = 0, missing = 0, partial = 0 }
- -------------------------------------------------------------------------------
- -- ARCHITECTS CUTTER ITEMS (Domum Ornamentum)
- -------------------------------------------------------------------------------
- local ARCHITECTS_CUTTER_ITEMS = {
- ["domum_ornamentum:panel"] = true,
- ["domum_ornamentum:plain"] = true,
- ["domum_ornamentum:post"] = true,
- ["domum_ornamentum:fancy_door"] = true,
- ["domum_ornamentum:fancy_light"] = true,
- ["domum_ornamentum:fancy_trapdoor"] = true,
- ["domum_ornamentum:blockpaperwall"] = true,
- ["domum_ornamentum:blockpillar"] = true,
- ["domum_ornamentum:blocktiledpaperwall"] = true,
- ["domum_ornamentum:blockypillar"] = true,
- ["domum_ornamentum:centerlight"] = true,
- ["domum_ornamentum:shingle"] = true,
- ["domum_ornamentum:shingle_slab"] = true,
- ["domum_ornamentum:shingle_stairs"] = true,
- ["domum_ornamentum:vanilla_fence_gate_compat"] = true,
- ["domum_ornamentum:vanilla_fence_compat"] = true,
- ["domum_ornamentum:vanilla_wall_compat"] = true,
- ["domum_ornamentum:vanilla_stairs_compat"] = true,
- ["domum_ornamentum:vanilla_slab_compat"] = true,
- ["domum_ornamentum:vanilla_trapdoors_compat"] = true,
- ["domum_ornamentum:framed"] = true,
- ["domum_ornamentum:framed_light"] = true,
- ["domum_ornamentum:four_light"] = true,
- ["domum_ornamentum:extra"] = true,
- ["domum_ornamentum:extra_slab"] = true,
- ["domum_ornamentum:extra_stairs"] = true,
- }
- -------------------------------------------------------------------------------
- -- HELPERS
- -------------------------------------------------------------------------------
- --- Safe peripheral call - prevents script crash on peripheral errors
- local function safeCall(fn, ...)
- local args = { ... }
- local ok, result = pcall(function()
- return fn(table.unpack(args))
- end)
- if not ok then
- print("[ERROR] Peripheral call failed: " .. tostring(result))
- return nil
- end
- return result
- end
- local function isBuilder(colonistName)
- if not colonistName then return false end
- return string.find(colonistName, "Builder") ~= nil
- end
- --- Equipment = tool/armor requests from colonists (desc contains "level")
- local function isEquipmentRequest(desc)
- if not desc then return false end
- return string.find(desc, "level") ~= nil
- end
- --- Build unique request ID from request data
- local function makeRequestId(data)
- -- Safely serialize nbt/components
- local dataStr = ""
- if data.item.nbt then
- local ok, s = pcall(textutils.serialize, data.item.nbt)
- if ok then dataStr = s end
- end
- if data.item.components then
- local ok, s = pcall(textutils.serialize, data.item.components)
- if ok then dataStr = dataStr .. s end
- end
- return string.format(
- "%s|%s|%s|%d|%s",
- data.item.name or "?",
- dataStr,
- data.colonist or "?",
- data.needed or 0,
- data.building or ""
- )
- end
- --- Extract variant name from Domum Ornamentum data
- --- Supports both legacy NBT and 1.21.1 Data Components
- local function getDomumVariant(itemData)
- if not itemData then return "Unknown" end
- -- 1.21.1 Data Components path
- local comp = itemData.components
- if comp and type(comp) == "table" then
- for _, key in ipairs({
- "domum_ornamentum:variant",
- "domum_ornamentum:block",
- "domum_ornamentum:material",
- "domum_ornamentum:texture",
- }) do
- if comp[key] then return tostring(comp[key]) end
- end
- end
- -- Legacy NBT path
- local nbt = itemData.nbt or itemData
- if type(nbt) == "table" then
- for _, field in ipairs({ "variant", "block", "material", "texture", "state" }) do
- if nbt[field] then return tostring(nbt[field]) end
- end
- end
- return "Unspecified. Check Requester"
- end
- local function isArchitectCutterItem(item)
- if not item or not item.name then return false end
- return ARCHITECTS_CUTTER_ITEMS[item.name] == true
- end
- --- Check if item has meaningful NBT or Components (1.21.1+)
- local function hasItemData(item)
- if not item then return false end
- if type(item.nbt) == "table" and next(item.nbt) ~= nil then return true end
- if type(item.components) == "table" and next(item.components) ~= nil then return true end
- if type(item.nbt) == "string" and #item.nbt > 0 then return true end
- return false
- end
- -------------------------------------------------------------------------------
- -- MONITOR UI
- -------------------------------------------------------------------------------
- local monWidth, monHeight = 0, 0
- local function drawHeader(mon, remaining)
- monWidth, monHeight = mon.getSize()
- local now = os.time()
- local cycle = "day"
- local color = colors.lightBlue
- if now >= 18.5 or now < 5 then
- cycle = "night"
- color = colors.red
- elseif now >= 18 then
- cycle = "sunset"
- color = colors.orange
- elseif now < 6 then
- cycle = "sunrise"
- color = colors.yellow
- end
- mon.setCursorPos(1, 1)
- mon.setTextColor(color)
- mon.write("Time: " .. textutils.formatTime(now, false) .. " [" .. cycle .. "]")
- local remainText = string.format("Next: %02ds", remaining)
- mon.setCursorPos(monWidth - #remainText + 1, 1)
- mon.setTextColor(colors.green)
- mon.write(remainText)
- end
- local function drawStats(mon, row)
- if row > monHeight then return row + 1 end
- local text = string.format(
- "Total: %d | Done: %d | Craft: %d | Partial: %d | Miss: %d",
- stats.total, stats.delivered, stats.crafting, stats.partial, stats.missing
- )
- mon.setCursorPos(1, row)
- mon.setTextColor(colors.lightGray)
- if #text > monWidth then text = text:sub(1, monWidth) end
- mon.write(text)
- return row + 1
- end
- local function drawSectionTitle(mon, row, title)
- if row > monHeight then return row + 1 end
- mon.setCursorPos(math.floor((monWidth - #title) / 2) + 1, row)
- mon.setTextColor(colors.white)
- mon.write(title)
- return row + 1
- end
- local function drawLine(mon, row, left, right, color)
- if row > monHeight then return row + 1 end
- local maxLeft = monWidth - #right - 1
- if #left > maxLeft and maxLeft > 3 then
- left = left:sub(1, maxLeft - 2) .. ".."
- end
- mon.setCursorPos(1, row)
- mon.setTextColor(color)
- mon.write(left)
- if #right > 0 then
- mon.setCursorPos(monWidth - #right + 1, row)
- mon.write(right)
- end
- return row + 1
- end
- -------------------------------------------------------------------------------
- -- REQUEST FETCH
- -------------------------------------------------------------------------------
- local function getRequests()
- local rawRequests = safeCall(colony.getRequests)
- if not rawRequests then
- print("[WARN] Failed to fetch colony requests")
- return {}
- end
- local list = {}
- for _, r in pairs(rawRequests) do
- if r.items and r.items[1] and r.items[1].name then
- local itemCount = r.count or 1
- table.insert(list, {
- name = r.name or "Unknown",
- desc = r.desc,
- needed = itemCount,
- -- Primary item
- item = {
- name = r.items[1].name,
- count = math.min(itemCount, MAX_EXPORT),
- nbt = r.items[1].nbt,
- components = r.items[1].components,
- },
- -- Keep ALL alternative items for equipment fallback
- allItems = r.items,
- colonist = r.target or "Unknown",
- building = r.building or r.target or "Unknown",
- })
- end
- end
- return list
- end
- -------------------------------------------------------------------------------
- -- EQUIPMENT DELIVERY
- -- Colonists request specific tools/armor (e.g. "Iron Pickaxe with level 2").
- -- The request's items[] list contains ALL acceptable alternatives.
- -- We try each alternative and export the first one RS has in stock.
- -------------------------------------------------------------------------------
- local function tryExportEquipment(data)
- -- Try every acceptable item alternative from the request
- local items = data.allItems or { data.item }
- for _, candidate in ipairs(items) do
- if candidate and candidate.name then
- local exportFilter = {
- name = candidate.name,
- count = math.min(data.needed, MAX_EXPORT),
- }
- -- Include NBT/components if present (for enchanted or specific items)
- if candidate.nbt then exportFilter.nbt = candidate.nbt end
- if candidate.components then exportFilter.components = candidate.components end
- local result = safeCall(bridge.exportItem, exportFilter, direction)
- if result and result > 0 then
- return result, candidate.name
- end
- end
- end
- return 0, (data.item.name or "?")
- end
- -------------------------------------------------------------------------------
- -- MAIN SCAN
- -------------------------------------------------------------------------------
- local function scan(mon, remaining)
- local builderEntries = {}
- local nonBuilderEntries = {}
- local equipmentEntries = {}
- local activeRequests = {}
- local missingDomum = {}
- stats = { total = 0, delivered = 0, crafting = 0, missing = 0, partial = 0 }
- local requests = getRequests()
- for _, data in ipairs(requests) do
- local requestId = makeRequestId(data)
- activeRequests[requestId] = true
- stats.total = stats.total + 1
- local provided = 0
- local color = colors.blue
- local isDomum = isArchitectCutterItem(data.item)
- local hasData = hasItemData(data.item)
- local isEquip = isEquipmentRequest(data.desc)
- local usedItem = data.item.name -- track which item was actually exported
- -- ======================================================
- -- DELIVERY
- -- ======================================================
- if isEquip then
- -- EQUIPMENT: try all acceptable alternatives from the request
- provided, usedItem = tryExportEquipment(data)
- elseif not isDomum or hasData then
- -- Normal items OR Domum with valid data
- local result = safeCall(bridge.exportItem, data.item, direction)
- provided = result or 0
- end
- -- Domum without data -> provided stays 0
- -- ======================================================
- -- DETERMINE STATUS
- -- ======================================================
- if provided >= data.needed then
- -- Fully delivered
- deliveredRequests[requestId] = true
- craftingRequests[requestId] = nil
- color = colors.green
- stats.delivered = stats.delivered + 1
- elseif provided > 0 then
- -- Partially delivered
- craftingRequests[requestId] = nil
- color = colors.yellow
- stats.partial = stats.partial + 1
- else
- -- Nothing delivered
- if isDomum and not hasData then
- color = colors.blue
- stats.missing = stats.missing + 1
- else
- -- Check if already crafting (prefer isItemCrafting if available)
- local alreadyCrafting = false
- if bridge.isItemCrafting then
- alreadyCrafting = safeCall(bridge.isItemCrafting, {
- name = data.item.name,
- nbt = data.item.nbt,
- }) or false
- end
- if alreadyCrafting or craftingRequests[requestId] then
- color = colors.pink
- stats.crafting = stats.crafting + 1
- -- Timeout stale craft jobs
- if craftingRequests[requestId] then
- local elapsed = os.epoch("utc") - (craftingRequests[requestId].started or 0)
- if elapsed > CRAFT_TIMEOUT_MS then
- print("[TIMEOUT] Stale craft: " .. data.item.name)
- craftingRequests[requestId] = nil
- color = colors.blue
- stats.crafting = stats.crafting - 1
- stats.missing = stats.missing + 1
- end
- end
- else
- -- Try to start crafting (not for equipment - those typically can't be auto-crafted)
- if not isEquip then
- local craftResult = safeCall(bridge.craftItem, {
- name = data.item.name,
- count = data.needed,
- nbt = data.item.nbt,
- })
- if craftResult then
- craftingRequests[requestId] = { started = os.epoch("utc") }
- color = colors.pink
- stats.crafting = stats.crafting + 1
- else
- color = colors.blue
- stats.missing = stats.missing + 1
- end
- else
- -- Equipment not in RS and can't be auto-crafted
- color = colors.blue
- stats.missing = stats.missing + 1
- end
- end
- end
- end
- -- ======================================================
- -- Domum Ornamentum Missing Items
- -- ======================================================
- if isDomum and color == colors.blue then
- local variant = getDomumVariant(data.item)
- local key = data.item.name .. "|" .. variant
- missingDomum[key] = {
- name = data.name:gsub("^[%d%-]+%s*", ""),
- variant = variant,
- }
- end
- -- ======================================================
- -- UI ENTRY
- -- ======================================================
- local variantStr = ""
- if isDomum then
- local v = getDomumVariant(data.item)
- if v:lower() ~= "unknown" then
- variantStr = " [" .. v .. "]"
- end
- end
- local cleanName = data.name:gsub("^[%d%-]+%s*", "")
- local entry = {
- left = string.format(
- "%d/%d %s%s",
- provided, data.needed, cleanName, variantStr
- ),
- provided = provided,
- needed = data.needed,
- color = color,
- }
- if isEquip then
- entry.right = data.colonist
- table.insert(equipmentEntries, entry)
- elseif isBuilder(data.colonist) then
- entry.right = data.building or "Builder"
- table.insert(builderEntries, entry)
- else
- entry.right = data.colonist
- table.insert(nonBuilderEntries, entry)
- end
- -- ======================================================
- -- TERMINAL OUTPUT
- -- ======================================================
- local target = entry.right or "?"
- if color == colors.green then
- print(string.format("[DONE] %s%s x%d -> %s", usedItem, variantStr, data.needed, target))
- elseif color == colors.pink then
- print(string.format("[CRAFTING] %s%s x%d -> %s", data.item.name, variantStr, data.needed, target))
- elseif color == colors.yellow then
- print(string.format("[PARTIAL] %s%s %d/%d -> %s", usedItem, variantStr, provided, data.needed, target))
- elseif isDomum and not hasData then
- print(string.format("[SKIP] %s x%d -> %s (no component data)", data.item.name, data.needed, target))
- elseif isEquip then
- print(string.format("[EQUIP] %s x%d -> %s (not in RS)", cleanName, data.needed, target))
- else
- print(string.format("[MISSING] %s x%d -> %s (no pattern)", data.item.name, data.needed, target))
- end
- end
- -- ======================================================
- -- CLEANUP: remove state for vanished requests
- -- FIX: collect keys first, then delete (safe Lua iteration)
- -- ======================================================
- local toRemove = {}
- for requestId, _ in pairs(deliveredRequests) do
- if not activeRequests[requestId] then
- table.insert(toRemove, requestId)
- end
- end
- for _, rid in ipairs(toRemove) do
- deliveredRequests[rid] = nil
- craftingRequests[rid] = nil
- end
- toRemove = {}
- for requestId, _ in pairs(craftingRequests) do
- if not activeRequests[requestId] then
- table.insert(toRemove, requestId)
- end
- end
- for _, rid in ipairs(toRemove) do
- craftingRequests[rid] = nil
- end
- -- ======================================================
- -- Sort: missing first, done last
- -- ======================================================
- local function sortByStatus(a, b)
- local order = { [colors.blue] = 1, [colors.pink] = 2, [colors.yellow] = 3, [colors.green] = 4 }
- return (order[a.color] or 0) < (order[b.color] or 0)
- end
- table.sort(builderEntries, sortByStatus)
- table.sort(nonBuilderEntries, sortByStatus)
- table.sort(equipmentEntries, sortByStatus)
- -- ======================================================
- -- MONITOR UI
- -- ======================================================
- mon.clear()
- drawHeader(mon, remaining)
- local row = 3
- row = drawStats(mon, row)
- row = row + 1
- if #builderEntries > 0 then
- row = drawSectionTitle(mon, row, "=== Builder Requests ===")
- for _, r in ipairs(builderEntries) do
- row = drawLine(mon, row, r.left, r.right, r.color)
- end
- row = row + 1
- end
- if #nonBuilderEntries > 0 then
- row = drawSectionTitle(mon, row, "=== Non-Builder Requests ===")
- for _, r in ipairs(nonBuilderEntries) do
- row = drawLine(mon, row, r.left, r.right, r.color)
- end
- row = row + 1
- end
- if #equipmentEntries > 0 then
- row = drawSectionTitle(mon, row, "=== Equipment Requests ===")
- for _, r in ipairs(equipmentEntries) do
- row = drawLine(mon, row, r.left, r.right, r.color)
- end
- row = row + 1
- end
- -- ======================================================
- -- MISSING DOMUM ORNAMENTUM SUMMARY
- -- ======================================================
- if next(missingDomum) ~= nil then
- row = drawSectionTitle(mon, row, "=== Missing Domum Ornamentum ===")
- for _, entry in pairs(missingDomum) do
- local text = "- " .. entry.name .. " [" .. entry.variant .. "]"
- row = drawLine(mon, row, text, "", colors.blue)
- end
- end
- -- Overflow warning
- if row > monHeight then
- mon.setCursorPos(1, monHeight)
- mon.setTextColor(colors.orange)
- mon.write(string.format(".. +%d lines (expand monitor)", row - monHeight))
- end
- end
- -------------------------------------------------------------------------------
- -- MAIN LOOP (with error recovery)
- -------------------------------------------------------------------------------
- local counter = time_between_runs
- local ok, err = pcall(scan, monitor, counter)
- if not ok then
- print("[ERROR] Initial scan failed: " .. tostring(err))
- print("[INFO] Retrying in " .. time_between_runs .. "s...")
- end
- local timer = os.startTimer(1)
- while true do
- local e = { os.pullEvent() }
- if e[1] == "timer" and e[2] == timer then
- counter = counter - 1
- if counter <= 0 then
- ok, err = pcall(scan, monitor, time_between_runs)
- if not ok then
- print("[ERROR] Scan failed: " .. tostring(err))
- pcall(findPeripherals)
- end
- counter = time_between_runs
- else
- pcall(drawHeader, monitor, counter)
- end
- timer = os.startTimer(1)
- elseif e[1] == "peripheral" or e[1] == "peripheral_detach" then
- print("[INFO] Peripheral change detected, re-scanning...")
- sleep(1)
- pcall(findPeripherals)
- pcall(scan, monitor, time_between_runs)
- counter = time_between_runs
- timer = os.startTimer(1)
- end
- end
RAW Paste Data
Copied
