-- -- dc.lua -- -- Reusable data-center subsystem logic. The Hostile Networks data center exposes -- only generic inventory IO ports (type hostilenetworks:data_center_io_port) - -- no controller peripheral, so there's no energy/progress/model-list to read. -- We can list what's in the ports and route items through them; that's it. -- -- local dc = require("dc").new(cfg) -- local st = dc.collect() -- what's in every port, classified -- local ok, err = dc.load_model("Blaze Data Model") -- -- MONITORING is role-agnostic (lists every port). CONTROL needs config to name -- which port is which and where items come from, because CC cannot read a port's -- in-world mode: -- dc_model_port, dc_input_port, dc_output_port -- port peripheral names -- dc_model_source, dc_input_source, dc_output_sink -- inventories to move to/from -- local dc = {} local MODEL_PATTERN = "data_model" function dc.new(cfg) cfg = cfg or {} local PORT_TYPE = cfg.port_type or "hostilenetworks:data_center_io_port" -- discover every IO port on the network local ports = {} for _, n in ipairs(peripheral.getNames()) do if peripheral.getType(n) == PORT_TYPE then ports[#ports + 1] = n end end if #ports == 0 then error("dc: no '" .. PORT_TYPE .. "' ports found on the network.", 0) end table.sort(ports, function (a, b) local na, nb = tonumber(a:match("_(%d+)$")), tonumber(b:match("_(%d+)$")) if na and nb then return na < nb end return a < b end) local portP = {} for _, n in ipairs(ports) do portP[n] = peripheral.wrap(n) end -- configured roles (optional; needed only for control) local model_port = cfg.dc_model_port local input_port = cfg.dc_input_port local output_port = cfg.dc_output_port local model_source = cfg.dc_model_source local input_source = cfg.dc_input_source local output_sink = cfg.dc_output_sink -- === monitoring === -- Classify one port's contents into models (grouped by real display name) -- and other items, with totals. local function classify(name) local p = portP[name] local models, items = {}, {} local modelCount, itemCount = 0, 0 local ok, list = pcall(p.list) if ok and list then for slot, item in pairs(list) do local ok2, det = pcall(p.getItemDetail, slot) local disp = (ok2 and det and det.displayName) or item.name if item.name:find(MODEL_PATTERN) then modelCount = modelCount + item.count models[disp] = (models[disp] or 0) + item.count else itemCount = itemCount + item.count items[disp] = (items[disp] or 0) + item.count end end end return name, models, items, modelCount, itemCount end local function mapToList(map) local out = {} for disp, count in pairs(map) do out[#out + 1] = { disp = disp, count = count } end table.sort(out, function (a, b) return a.disp < b.disp end) return out end -- Spare trained models available to load, grouped by display name, from the -- shared model barrel. Feeds the coordinator's per-DC load-model picker. local function scanSourceModels() if not model_source then return {} end local p = peripheral.wrap(model_source) if not (p and p.list) then return {} end local byName = {} local ok, list = pcall(p.list) if ok and list then for slot, item in pairs(list) do if item.name:find(MODEL_PATTERN) then local ok2, det = pcall(p.getItemDetail, slot) local disp = (ok2 and det and det.displayName) or "Data Model" byName[disp] = (byName[disp] or 0) + item.count end end end return mapToList(byName) end local function collect() local perPort, allModels, byName = {}, {}, {} local totalModels, totalItems = 0, 0 for _, n in ipairs(ports) do local name, models, items, mc, ic = classify(n) perPort[#perPort + 1] = { name = name, models = mc, items = ic } byName[name] = { models = mc, items = ic } totalModels = totalModels + mc totalItems = totalItems + ic for disp, count in pairs(models) do allModels[disp] = (allModels[disp] or 0) + count end end -- role-labeled ports (only where config named them; the coordinator can't -- infer which port is which, so the RTU - which knows - reports it) local roles = { input = input_port and byName[input_port] or nil, model = model_port and byName[model_port] or nil, output = output_port and byName[output_port] or nil, } return { headline = string.format("%d models loaded, %d items", totalModels, totalItems), totals = { models = totalModels, items = totalItems, ports = #ports }, models = mapToList(allModels), -- [{disp,count}] loaded across all ports ports = perPort, -- [{name, models, items}] roles = roles, -- { input?, model?, output? } = {models,items} available = scanSourceModels(), -- [{disp,count}] loadable from the barrel } end -- === control (config-driven; clear errors when a role isn't set up) === -- Move `count` of the item in `srcName`'s slot matching `match` into `dstName`. -- `match` is nil (first non-empty slot), an item id substring, or an exact -- displayName. Returns ok, err. local function moveMatching(srcName, dstName, count, match, byDisplay) if not srcName then return false, "no source configured" end if not dstName then return false, "no target port configured" end local srcP = peripheral.wrap(srcName) if not srcP then return false, "source '" .. srcName .. "' not found" end local ok, list = pcall(srcP.list) if not ok or not list then return false, "source list failed" end local slot for s, item in pairs(list) do if match == nil then slot = s; break elseif byDisplay then local ok2, det = pcall(srcP.getItemDetail, s) if ok2 and det and det.displayName == match then slot = s; break end elseif item.name:find(match, 1, true) then slot = s; break end end if not slot then return false, "nothing matching in source" end local moved = srcP.pushItems(dstName, slot, count) if not moved or moved == 0 then return false, "push failed (port full / wrong mode?)" end return true, { moved = moved } end local function loadModel(disp) if not disp then return false, "no model given" end return moveMatching(model_source, model_port, 1, disp, true) end -- Feed inputs: a specific item id (substring) or just whatever's next in the -- source; count defaults to a stack. local function feedInput(item, count) return moveMatching(input_source, input_port, count or 64, item, false) end -- Drain the output port into the sink, slot by slot. local function pullOutput() if not output_port or not portP[output_port] then return false, "no output port configured" end if not output_sink then return false, "no output sink configured" end local p = portP[output_port] local ok, list = pcall(p.list) if not ok or not list then return false, "output list failed" end local moved = 0 for slot in pairs(list) do local n = p.pushItems(output_sink, slot) moved = moved + (n or 0) end return true, { moved = moved } end return { ports = ports, collect = collect, load_model = loadModel, feed_input = feedInput, pull_output = pullOutput, } end return dc