Add Andesite Alloy factory controller
This commit is contained in:
@@ -61,6 +61,32 @@ Controller-Koordinaten `11266/64/10810`, `11260/64/10810` und `11254/64/10810` z
|
|||||||
Fehlt ein Reader, bleibt dadurch exakt die betroffene Boiler-Zeile auf `MISSING / NO READER`.
|
Fehlt ein Reader, bleibt dadurch exakt die betroffene Boiler-Zeile auf `MISSING / NO READER`.
|
||||||
Jeder Block Reader muss direkt auf den Controller-Tank seines Boiler-Multiblocks zeigen.
|
Jeder Block Reader muss direkt auf den Controller-Tank seines Boiler-Multiblocks zeigen.
|
||||||
|
|
||||||
|
## Andesite-Alloy-Produktion
|
||||||
|
|
||||||
|
Der erste lokale Create-Produktionscontroller ueberwacht einen Zink-Nugget-Vault, einen
|
||||||
|
Andesite-Alloy-Vault und die per Clutch getrennte Maschinenwelle. Die beiden Vaults muessen
|
||||||
|
jeweils direkt ueber einen aktivierten Wired Modem Full Block als Inventory-Peripheral mit dem
|
||||||
|
Computer verbunden sein. Ein Advanced-Peripherals Block Reader zeigt auf das Stressometer der
|
||||||
|
Maschinenwelle. Der Redstone-Ausgang `left` steuert die Clutch ueber einen Fail-safe-Inverter.
|
||||||
|
|
||||||
|
Installation und erster Start:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
wget https://git.peli-server.de/Dystroyer8/ATM10_CC_Codes/raw/branch/main/create_andesite_display.lua create_andesite_display.lua
|
||||||
|
create_andesite_display
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Controller erkennt die Vaults anhand von `create:zinc_nugget` und
|
||||||
|
`create:andesite_alloy` und speichert ihre Peripheral-Namen. Die Freigabe wird ebenfalls
|
||||||
|
gespeichert. Fehlendes Zink, ein voller Ausgang, fehlende Vault-Daten oder eine ueberlastete
|
||||||
|
Welle schalten die Maschinenwelle ab; nach Behebung wird eine weiterhin freigegebene Anlage
|
||||||
|
automatisch fortgesetzt. Fuer den automatischen Start:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
wget https://git.peli-server.de/Dystroyer8/ATM10_CC_Codes/raw/branch/main/create_andesite_startup.lua startup.lua
|
||||||
|
reboot
|
||||||
|
```
|
||||||
|
|
||||||
## Struktur
|
## Struktur
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -0,0 +1,354 @@
|
|||||||
|
-- Andesite Alloy production controller for CC:Tweaked.
|
||||||
|
-- Two Create Item Vaults are discovered by their contents and remembered.
|
||||||
|
|
||||||
|
local REFRESH_SECONDS = 1
|
||||||
|
local MIN_WIDTH = 42
|
||||||
|
local MIN_HEIGHT = 15
|
||||||
|
local CLUTCH_OUTPUT_SIDE = "left"
|
||||||
|
local STATE_FILE = "create_andesite_state.txt"
|
||||||
|
local DEVICE_FILE = "create_andesite_devices.txt"
|
||||||
|
|
||||||
|
local ZINC_ITEMS = {
|
||||||
|
["create:zinc_nugget"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local ALLOY_ITEMS = {
|
||||||
|
["create:andesite_alloy"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local button = { x1 = 0, x2 = 0, y = 0 }
|
||||||
|
|
||||||
|
local function loadText(path)
|
||||||
|
if not fs.exists(path) then return nil end
|
||||||
|
local handle = fs.open(path, "r")
|
||||||
|
if not handle then return nil end
|
||||||
|
local value = handle.readAll()
|
||||||
|
handle.close()
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function saveText(path, value)
|
||||||
|
local handle = fs.open(path, "w")
|
||||||
|
if not handle then return false end
|
||||||
|
handle.write(value)
|
||||||
|
handle.close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local requestedRunning = loadText(STATE_FILE) == "RUNNING"
|
||||||
|
local devices = textutils.unserialize(loadText(DEVICE_FILE) or "") or {}
|
||||||
|
local capacityCache = {}
|
||||||
|
|
||||||
|
-- Fail safe while peripherals are being discovered.
|
||||||
|
redstone.setOutput(CLUTCH_OUTPUT_SIDE, false)
|
||||||
|
|
||||||
|
local monitor = peripheral.find("monitor")
|
||||||
|
if not monitor then
|
||||||
|
error("Kein Monitor gefunden. Monitor per Wired Modem verbinden.", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fitMonitor()
|
||||||
|
local chosen = 0.5
|
||||||
|
for step = 10, 1, -1 do
|
||||||
|
local scale = step / 2
|
||||||
|
local ok = pcall(monitor.setTextScale, scale)
|
||||||
|
if ok then
|
||||||
|
local width, height = monitor.getSize()
|
||||||
|
if width >= MIN_WIDTH and height >= MIN_HEIGHT then
|
||||||
|
chosen = scale
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
monitor.setTextScale(chosen)
|
||||||
|
monitor.setCursorBlink(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
fitMonitor()
|
||||||
|
|
||||||
|
local function center(y, text, foreground, background)
|
||||||
|
local width = monitor.getSize()
|
||||||
|
text = tostring(text or "")
|
||||||
|
if #text > width then text = text:sub(1, width) end
|
||||||
|
local x = math.max(1, math.floor((width - #text) / 2) + 1)
|
||||||
|
monitor.setBackgroundColor(background or colors.black)
|
||||||
|
monitor.setTextColor(foreground or colors.white)
|
||||||
|
monitor.setCursorPos(1, y)
|
||||||
|
monitor.write((" "):rep(width))
|
||||||
|
monitor.setCursorPos(x, y)
|
||||||
|
monitor.write(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatInteger(value)
|
||||||
|
value = math.floor(math.abs(tonumber(value) or 0) + 0.5)
|
||||||
|
local digits, parts = tostring(value), {}
|
||||||
|
while #digits > 3 do
|
||||||
|
table.insert(parts, 1, digits:sub(-3))
|
||||||
|
digits = digits:sub(1, -4)
|
||||||
|
end
|
||||||
|
table.insert(parts, 1, digits)
|
||||||
|
return table.concat(parts, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getValueIgnoringCase(data, wantedKeys)
|
||||||
|
if type(data) ~= "table" then return nil end
|
||||||
|
for key, value in pairs(data) do
|
||||||
|
local lowered = tostring(key):lower()
|
||||||
|
for _, wanted in ipairs(wantedKeys) do
|
||||||
|
if lowered == wanted:lower() then return value end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isVault(name)
|
||||||
|
return peripheral.isPresent(name)
|
||||||
|
and peripheral.hasType(name, "inventory")
|
||||||
|
and peripheral.hasType(name, "create:item_vault")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getInventory(name)
|
||||||
|
if not name or not isVault(name) then return nil end
|
||||||
|
local okList, listed = pcall(peripheral.call, name, "list")
|
||||||
|
local okSize, size = pcall(peripheral.call, name, "size")
|
||||||
|
if not okList or not okSize or type(listed) ~= "table" then return nil end
|
||||||
|
|
||||||
|
local result = {
|
||||||
|
name = name,
|
||||||
|
list = listed,
|
||||||
|
size = tonumber(size) or 0,
|
||||||
|
capacity = 0,
|
||||||
|
total = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
local cached = capacityCache[name]
|
||||||
|
if cached and cached.size == result.size then
|
||||||
|
result.capacity = cached.capacity
|
||||||
|
else
|
||||||
|
for slot = 1, result.size do
|
||||||
|
local ok, limit = pcall(peripheral.call, name, "getItemLimit", slot)
|
||||||
|
result.capacity = result.capacity + (ok and tonumber(limit) or 64)
|
||||||
|
end
|
||||||
|
capacityCache[name] = { size = result.size, capacity = result.capacity }
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, item in pairs(listed) do
|
||||||
|
result.total = result.total + (tonumber(item.count) or 0)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function countItems(inventory, wanted)
|
||||||
|
if not inventory then return 0 end
|
||||||
|
local count = 0
|
||||||
|
for _, item in pairs(inventory.list) do
|
||||||
|
if wanted[item.name] then count = count + (tonumber(item.count) or 0) end
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
local function containsItems(inventory, wanted)
|
||||||
|
return countItems(inventory, wanted) > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function discoverVaults()
|
||||||
|
local oldZinc, oldAlloy = devices.zinc, devices.alloy
|
||||||
|
local savedZinc = getInventory(devices.zinc)
|
||||||
|
local savedAlloy = getInventory(devices.alloy)
|
||||||
|
|
||||||
|
for _, name in ipairs(peripheral.getNames()) do
|
||||||
|
if isVault(name) then
|
||||||
|
local inventory = getInventory(name)
|
||||||
|
if inventory then
|
||||||
|
if containsItems(inventory, ZINC_ITEMS) then
|
||||||
|
devices.zinc = name
|
||||||
|
savedZinc = inventory
|
||||||
|
end
|
||||||
|
if containsItems(inventory, ALLOY_ITEMS) then
|
||||||
|
devices.alloy = name
|
||||||
|
savedAlloy = inventory
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if devices.zinc ~= oldZinc or devices.alloy ~= oldAlloy then
|
||||||
|
saveText(DEVICE_FILE, textutils.serialize(devices))
|
||||||
|
end
|
||||||
|
return savedZinc or getInventory(devices.zinc), savedAlloy or getInventory(devices.alloy)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function discoverStressometer()
|
||||||
|
for _, name in ipairs(peripheral.getNames()) do
|
||||||
|
if peripheral.hasType(name, "block_reader") then
|
||||||
|
local ok, data = pcall(peripheral.call, name, "getBlockData")
|
||||||
|
if ok and type(data) == "table" and tonumber(getValueIgnoringCase(data, { "Speed" })) then
|
||||||
|
local network = getValueIgnoringCase(data, { "Network" })
|
||||||
|
local speed = math.abs(tonumber(getValueIgnoringCase(data, { "Speed" })) or 0)
|
||||||
|
if type(network) == "table" then
|
||||||
|
local capacity = tonumber(getValueIgnoringCase(network, { "Capacity" })) or 0
|
||||||
|
local usage = tonumber(getValueIgnoringCase(network, { "Stress" })) or 0
|
||||||
|
local load = capacity > 0 and usage / capacity * 100 or 0
|
||||||
|
return speed, capacity, usage, load
|
||||||
|
end
|
||||||
|
return speed, nil, nil, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, nil, nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function percentage(value, maximum)
|
||||||
|
if not maximum or maximum <= 0 then return 0 end
|
||||||
|
return math.min(100, value / maximum * 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function evaluate()
|
||||||
|
local zincVault, alloyVault = discoverVaults()
|
||||||
|
local zinc = countItems(zincVault, ZINC_ITEMS)
|
||||||
|
local alloy = countItems(alloyVault, ALLOY_ITEMS)
|
||||||
|
local speed, capacity, usage, load = discoverStressometer()
|
||||||
|
|
||||||
|
local missing = not zincVault or not alloyVault
|
||||||
|
local noMaterial = zincVault and zinc <= 0
|
||||||
|
local outputFull = alloyVault and alloyVault.capacity > 0 and alloyVault.total >= alloyVault.capacity
|
||||||
|
local overstressed = capacity and usage and usage > capacity
|
||||||
|
local allowed = not missing and not noMaterial and not outputFull and not overstressed
|
||||||
|
local effectiveRunning = requestedRunning and allowed
|
||||||
|
|
||||||
|
redstone.setOutput(CLUTCH_OUTPUT_SIDE, effectiveRunning)
|
||||||
|
|
||||||
|
local status, color = "STOPPED", colors.red
|
||||||
|
if requestedRunning then
|
||||||
|
if missing then status, color = "NO VAULT DATA", colors.red
|
||||||
|
elseif noMaterial then status, color = "WAITING: NO ZINC", colors.orange
|
||||||
|
elseif outputFull then status, color = "STANDBY: OUTPUT FULL", colors.orange
|
||||||
|
elseif overstressed then status, color = "STOPPED: OVERSTRESSED", colors.red
|
||||||
|
elseif not speed or speed == 0 then status, color = "STARTING / NO ROTATION", colors.orange
|
||||||
|
else status, color = "RUNNING", colors.lime end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
zincVault = zincVault,
|
||||||
|
alloyVault = alloyVault,
|
||||||
|
zinc = zinc,
|
||||||
|
alloy = alloy,
|
||||||
|
speed = speed,
|
||||||
|
capacity = capacity,
|
||||||
|
usage = usage,
|
||||||
|
load = load,
|
||||||
|
status = status,
|
||||||
|
statusColor = color,
|
||||||
|
effectiveRunning = effectiveRunning,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawInventory(y, label, count, inventory, color)
|
||||||
|
if not inventory then
|
||||||
|
center(y, label .. ": NO DATA", colors.red)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local fill = percentage(inventory.total, inventory.capacity)
|
||||||
|
local fillText = ("%.1f"):format(fill):gsub("%.", ",")
|
||||||
|
local text = ("%s: %s / %s %s%%"):format(
|
||||||
|
label,
|
||||||
|
formatInteger(count),
|
||||||
|
formatInteger(inventory.capacity),
|
||||||
|
fillText
|
||||||
|
)
|
||||||
|
center(y, text, color or colors.white)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawButton(height, data)
|
||||||
|
local width = monitor.getSize()
|
||||||
|
local label = requestedRunning and " CONTROL: ENABLED " or " CONTROL: DISABLED "
|
||||||
|
local x = math.max(1, math.floor((width - #label) / 2) + 1)
|
||||||
|
local y = math.max(14, height - 1)
|
||||||
|
|
||||||
|
button.x1 = x
|
||||||
|
button.x2 = math.min(width, x + #label - 1)
|
||||||
|
button.y = y
|
||||||
|
|
||||||
|
monitor.setCursorPos(1, y)
|
||||||
|
monitor.setBackgroundColor(colors.black)
|
||||||
|
monitor.write((" "):rep(width))
|
||||||
|
monitor.setCursorPos(x, y)
|
||||||
|
monitor.setBackgroundColor(requestedRunning and (data.effectiveRunning and colors.lime or colors.orange) or colors.red)
|
||||||
|
monitor.setTextColor(colors.black)
|
||||||
|
monitor.write(label)
|
||||||
|
monitor.setBackgroundColor(colors.black)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function render()
|
||||||
|
local _, height = monitor.getSize()
|
||||||
|
local data = evaluate()
|
||||||
|
|
||||||
|
monitor.setBackgroundColor(colors.black)
|
||||||
|
monitor.clear()
|
||||||
|
center(1, "ANDESITE ALLOY FACTORY", colors.cyan)
|
||||||
|
|
||||||
|
drawInventory(3, "ZINC NUGGETS", data.zinc, data.zincVault,
|
||||||
|
data.zinc <= 0 and colors.orange or colors.lime)
|
||||||
|
drawInventory(4, "ANDESITE ALLOY", data.alloy, data.alloyVault,
|
||||||
|
data.alloyVault and data.alloyVault.total >= data.alloyVault.capacity and colors.orange or colors.lime)
|
||||||
|
|
||||||
|
center(6, "MACHINE SHAFT", colors.cyan)
|
||||||
|
if data.speed ~= nil then
|
||||||
|
center(7, ("Speed: %.1f RPM"):format(data.speed), data.speed > 0 and colors.lime or colors.orange)
|
||||||
|
else
|
||||||
|
center(7, "Speed: NO DATA", colors.red)
|
||||||
|
end
|
||||||
|
if data.capacity then
|
||||||
|
center(8, "Capacity: " .. formatInteger(data.capacity) .. " SU", colors.white)
|
||||||
|
center(9, "Usage: " .. formatInteger(data.usage) .. " SU", colors.white)
|
||||||
|
local loadText = ("Load: %.1f %%"):format(data.load):gsub("%.", ",")
|
||||||
|
center(10, loadText, data.load >= 90 and colors.red or data.load >= 70 and colors.orange or colors.lime)
|
||||||
|
else
|
||||||
|
center(8, "Shaft network offline", colors.gray)
|
||||||
|
end
|
||||||
|
|
||||||
|
center(12, data.status, data.statusColor)
|
||||||
|
drawButton(height, data)
|
||||||
|
center(height, "Touch = Anlage freigeben/stoppen", colors.gray)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function safeRender()
|
||||||
|
local ok, reason = pcall(render)
|
||||||
|
if not ok then
|
||||||
|
redstone.setOutput(CLUTCH_OUTPUT_SIDE, false)
|
||||||
|
monitor.setBackgroundColor(colors.black)
|
||||||
|
monitor.clear()
|
||||||
|
center(1, "CONTROLLER ERROR", colors.red)
|
||||||
|
center(3, tostring(reason), colors.orange)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function toggleControl()
|
||||||
|
requestedRunning = not requestedRunning
|
||||||
|
saveText(STATE_FILE, requestedRunning and "RUNNING" or "STOPPED")
|
||||||
|
end
|
||||||
|
|
||||||
|
safeRender()
|
||||||
|
local refreshTimer = os.startTimer(REFRESH_SECONDS)
|
||||||
|
|
||||||
|
local function run()
|
||||||
|
while true do
|
||||||
|
local event, first, second, third = os.pullEventRaw()
|
||||||
|
if event == "terminate" then
|
||||||
|
return
|
||||||
|
elseif event == "timer" and first == refreshTimer then
|
||||||
|
safeRender()
|
||||||
|
refreshTimer = os.startTimer(REFRESH_SECONDS)
|
||||||
|
elseif event == "monitor_touch" then
|
||||||
|
local x, y = second, third
|
||||||
|
if y == button.y and x >= button.x1 and x <= button.x2 then
|
||||||
|
toggleControl()
|
||||||
|
safeRender()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, reason = pcall(run)
|
||||||
|
redstone.setOutput(CLUTCH_OUTPUT_SIDE, false)
|
||||||
|
if not ok then error(reason, 0) end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- Startup wrapper for the local Andesite Alloy production controller.
|
||||||
|
|
||||||
|
if not fs.exists("create_andesite_display.lua") then
|
||||||
|
printError("create_andesite_display.lua fehlt")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
shell.run("create_andesite_display.lua")
|
||||||
@@ -27,8 +27,10 @@ for _, name in ipairs(names) do
|
|||||||
writeLine("Methods: " .. table.concat(methods, ", "))
|
writeLine("Methods: " .. table.concat(methods, ", "))
|
||||||
|
|
||||||
local hasBlockData = false
|
local hasBlockData = false
|
||||||
|
local hasInventoryList = false
|
||||||
for _, method in ipairs(methods) do
|
for _, method in ipairs(methods) do
|
||||||
if method == "getBlockData" then hasBlockData = true end
|
if method == "getBlockData" then hasBlockData = true end
|
||||||
|
if method == "list" then hasInventoryList = true end
|
||||||
end
|
end
|
||||||
|
|
||||||
if hasBlockData then
|
if hasBlockData then
|
||||||
@@ -36,6 +38,13 @@ for _, name in ipairs(names) do
|
|||||||
writeLine("getBlockData OK: " .. tostring(ok))
|
writeLine("getBlockData OK: " .. tostring(ok))
|
||||||
writeLine(ok and textutils.serialize(data) or tostring(data))
|
writeLine(ok and textutils.serialize(data) or tostring(data))
|
||||||
end
|
end
|
||||||
|
if hasInventoryList then
|
||||||
|
local okSize, size = pcall(peripheral.call, name, "size")
|
||||||
|
local okList, items = pcall(peripheral.call, name, "list")
|
||||||
|
writeLine("Inventory size: " .. (okSize and tostring(size) or "ERROR"))
|
||||||
|
writeLine("Inventory list OK: " .. tostring(okList))
|
||||||
|
writeLine(okList and textutils.serialize(items) or tostring(items))
|
||||||
|
end
|
||||||
writeLine("")
|
writeLine("")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ return {
|
|||||||
files = {
|
files = {
|
||||||
"startup.lua",
|
"startup.lua",
|
||||||
"create_boiler_display.lua",
|
"create_boiler_display.lua",
|
||||||
|
"create_andesite_display.lua",
|
||||||
|
"create_andesite_startup.lua",
|
||||||
"create_peripheral_debug.lua",
|
"create_peripheral_debug.lua",
|
||||||
"core/constants.lua",
|
"core/constants.lua",
|
||||||
"core/log.lua",
|
"core/log.lua",
|
||||||
|
|||||||
Reference in New Issue
Block a user