Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
local peripheralNames = { reactor = "draconic_reactor_1", inGate = "flow_gate_3", outGate = "flow_gate_2", monitor = "right" } local autoStop = 0.1 local setfield = 0.05 local setTemp = 8000.4 local fieldCoe = {0.08, 0.03, 0.09} local tempCoe = {0.05, 0.09, 0.1} local energyCoe = {0.045, 0.11, 0.1} local intgTime = 15 local TICK = 0.05 local FIELD_INTERVAL = math.floor(0.3 / TICK + 0.5) local TEMP_INTERVAL = math.floor(0.8 / TICK + 0.5) -- Safety thresholds local MAX_TEMP = 8300 local MIN_FIELD_RATE = 0.02 -- 2% local reactor, inGate, outGate, monitor = nil, nil, nil, nil local curIn, curOut = 0, 0 local avgOut = 0 local fieldDeltaList = {} local tempDeltaList = {} local energyDeltaList = {} local state = "idle" -- idle, check, attach, charge, active, error local buttonState = "stop" -- start/stop text toggling on GUI local running = false -- whether PID control is active / we have 'started' reactor control -- PID internal trackers local field_it, field_eI, field_lastDelta = 0, 0.0, 0.0 local temp_it, temp_eI, temp_lastDelta = 0, 0.0, 0.0 local energy_it, energy_eI, energy_lastDelta = 0, 0.0, 0.0 -- counters for tick-based updates local tickCounter = 0 local fieldCounter = 0 local tempCounter = 0 -- monitor layout (3x2 proportion) local mon_w, mon_h = nil, nil local seg_w, seg_h = nil, nil ------------------------------------------------------------------------------- -- Helper: safe call - check method exists before calling ------------------------------------------------------------------------------- local function hasMethod(obj, name) if not obj then return false end return type(obj[name]) == "function" end local function safeCall(obj, name, ...) if not obj then return nil, "nil object" end local f = obj[name] if type(f) ~= "function" then return nil, "method_missing:" .. tostring(name) end return pcall(f, obj, ...) end ------------------------------------------------------------------------------- -- Peripheral detection helpers ------------------------------------------------------------------------------- local function tryWrap(name) if name and name ~= "" then local ok, res = pcall(peripheral.wrap, name) if ok and res then return res end end return nil end local function findReactorPeripheral() for n, t in pairs(peripheral.getNames()) do local p = peripheral.wrap(t) if p and type(p.getReactorInfo) == "function" then return t, p end end return nil, nil end local function findGatePeripheral(exclude) for n, t in pairs(peripheral.getNames()) do if t ~= exclude then local p = peripheral.wrap(t) if p then -- attempt to find a method that looks like flow control if (type(p.setFlow) == "function") or (type(p.setFlowOverride) == "function") or (type(p.setFlowRate) == "function") or (type(p.setOverrideEnabled) == "function") then return t, p end end end end return nil, nil end local function findMonitorPeripheral() for n, t in pairs(peripheral.getNames()) do local p = peripheral.wrap(t) if p and type(p.clear) == "function" and type(p.setCursorPos) == "function" then return t, p end end return nil, nil end ------------------------------------------------------------------------------- -- Peripheral assignment / self-check ------------------------------------------------------------------------------- local function detectPeripherals() -- attempt to wrap via explicit names first reactor = tryWrap(peripheralNames.reactor) inGate = tryWrap(peripheralNames.inGate) outGate = tryWrap(peripheralNames.outGate) monitor = tryWrap(peripheralNames.monitor) -- auto-detect any that are still nil if not reactor then local name, p = findReactorPeripheral() if p then reactor = p peripheralNames.reactor = name end end if not inGate then local name, p = findGatePeripheral(peripheralNames.outGate) if p then inGate = p peripheralNames.inGate = name end end if not outGate then local name, p = findGatePeripheral(peripheralNames.inGate) if p then outGate = p peripheralNames.outGate = name end end if not monitor then local name, p = findMonitorPeripheral() if p then monitor = p peripheralNames.monitor = name end end end local function checkAPIs() -- return true if basic APIs exist if not reactor then return false, "no reactor" end if not hasMethod(reactor, "getReactorInfo") then return false, "reactor.getReactorInfo missing" end -- reactor methods may vary, but check some common ones if not (hasMethod(reactor, "activateReactor") and hasMethod(reactor, "stopReactor") and hasMethod(reactor, "chargeReactor")) then -- still allow if some present: require at least getReactorInfo -- but prefer to have activate/stop/charge end if not inGate or not outGate then return false, "missing gates" end -- check flow methods on gates local inOk = hasMethod(inGate, "setFlow") or hasMethod(inGate, "setFlowOverride") or hasMethod(inGate, "setFlowRate") or hasMethod(inGate, "setOverrideEnabled") local outOk = hasMethod(outGate, "setFlow") or hasMethod(outGate, "setFlowOverride") or hasMethod(outGate, "setFlowRate") or hasMethod(outGate, "setOverrideEnabled") if not (inOk and outOk) then return false, "gate methods missing" end if monitor then local monOk = hasMethod(monitor, "clear") and hasMethod(monitor, "setCursorPos") and hasMethod(monitor, "write") if not monOk then return false, "monitor methods missing" end end return true, "ok" end ------------------------------------------------------------------------------- -- Flow control wrappers (tolerant to different gate APIs) ------------------------------------------------------------------------------- local function setIn(value) if not inGate then return false end if value < 0 then value = 0 end -- clamp to a large max if value > 64000000 then value = 64000000 end curIn = value -- try several possible methods if hasMethod(inGate, "setFlowOverride") then pcall(inGate.setFlowOverride, inGate, value) return true elseif hasMethod(inGate, "setFlow") then pcall(inGate.setFlow, inGate, value) return true elseif hasMethod(inGate, "setFlowRate") then pcall(inGate.setFlowRate, inGate, value) return true elseif hasMethod(inGate, "setOverrideEnabled") then -- set override, then set flow maybe via setFlow or similar pcall(inGate.setOverrideEnabled, inGate, true) if hasMethod(inGate, "setFlow") then pcall(inGate.setFlow, inGate, value) end return true else return false end end local function setOut(value) if not outGate then return false end if value < 0 then value = 0 end if value > 64000000 then value = 64000000 end curOut = value if hasMethod(outGate, "setFlowOverride") then pcall(outGate.setFlowOverride, outGate, value) return true elseif hasMethod(outGate, "setFlow") then pcall(outGate.setFlow, outGate, value) return true elseif hasMethod(outGate, "setFlowRate") then pcall(outGate.setFlowRate, outGate, value) return true elseif hasMethod(outGate, "setOverrideEnabled") then pcall(outGate.setOverrideEnabled, outGate, true) if hasMethod(outGate, "setFlow") then pcall(outGate.setFlow, outGate, value) end return true else return false end end ------------------------------------------------------------------------------- -- Reactor info helpers (wrap original names) ------------------------------------------------------------------------------- local function getInfo() if not reactor then return nil end local ok, info = pcall(reactor.getReactorInfo, reactor) if ok and type(info) == "table" then return info end return nil end local function status() local info = getInfo() if not info then return nil end return info.status end local function Temp() local info = getInfo() if not info then return 0 end return info.temperature or 0 end local function Field() local info = getInfo() if not info then return 0 end return info.fieldStrength or 0 end local function maxField() local info = getInfo() if not info then return 1 end return info.maxFieldStrength or 1 end local function FieldRate() local info = getInfo() if not info then return 0 end if info.maxFieldStrength and info.maxFieldStrength > 0 then return (info.fieldStrength or 0) / info.maxFieldStrength end return 0 end local function FieldDrain() local info = getInfo() if not info then return 0 end return info.fieldDrainRate or 0 end local function Energy() local info = getInfo() if not info then return 0 end return info.energySaturation or 0 end local function EnergyRate() local info = getInfo() if not info then return 0 end if info.maxEnergySaturation and info.maxEnergySaturation > 0 then return (info.energySaturation or 0) / info.maxEnergySaturation end return 0 end local function FuelRate() local info = getInfo() if not info then return 0 end if info.maxFuelConversion and info.maxFuelConversion > 0 then return (info.fuelConversion or 0) / info.maxFuelConversion end return 0 end local function eV() local info = getInfo() if not info then return 0 end return info.generationRate or 0 end local function startReactor() if not reactor then return false end if hasMethod(reactor, "activateReactor") then pcall(reactor.activateReactor, reactor) return true end return false end local function stopReactor() if not reactor then return false end if hasMethod(reactor, "stopReactor") then pcall(reactor.stopReactor, reactor) return true end return false end local function chargeReactor() if not reactor then return false end if hasMethod(reactor, "chargeReactor") then pcall(reactor.chargeReactor, reactor) return true end return false end ------------------------------------------------------------------------------- -- Initialize reactor for activation (charging, override enabling) ------------------------------------------------------------------------------- local function initializeReactor() if inGate and hasMethod(inGate, "setOverrideEnabled") then pcall(inGate.setOverrideEnabled, inGate, true) end if outGate and hasMethod(outGate, "setOverrideEnabled") then pcall(outGate.setOverrideEnabled, outGate, true) end chargeReactor() setOut(0) setIn(64000000) -- try to activate if thresholds satisfied for i = 1, 200 do -- timeout loop (200*0.05 = 10s) local t = Temp() local f = Field() local e = Energy() if t >= 2000 and f >= 0.49 and e >= 0.49 then startReactor() if status() == "running" then return true end end os.sleep(TICK) end return false end ------------------------------------------------------------------------------- -- PID update functions (called at their respective intervals) ------------------------------------------------------------------------------- local function sum(tb) local s = 0.0 for _, v in ipairs(tb) do s = s + v end return s end local function len(tb) return #tb end local function updateFieldPID() -- fieldDelta relative to maxField local fieldDelta = maxField() * setfield - Field() local eP = fieldDelta * fieldCoe[1] -- only integrate if eP + drain >= 0 (mimic original logic) if eP + FieldDrain() >= 0 then table.insert(fieldDeltaList, fieldDelta) if field_it >= 3 and #fieldDeltaList >= 2 then field_eI = sum(fieldDeltaList) * fieldCoe[2] local last = fieldDeltaList[#fieldDeltaList - 1] or fieldDeltaList[#fieldDeltaList] local eD = (fieldDelta - last) * fieldCoe[3] local eT = eP + field_eI + eD setIn(eT + FieldDrain()) field_lastDelta = fieldDelta else -- accumulate until integral starts if field_it <= intgTime then field_it = field_it + 1 end -- still compute proportional-only local eT = eP + field_eI setIn(eT + FieldDrain()) field_lastDelta = fieldDelta table.insert(fieldDeltaList, fieldDelta) end -- limit integral history if #fieldDeltaList > intgTime then while #fieldDeltaList > intgTime do table.remove(fieldDeltaList, 1) end end else -- if not allowed to integrate, still set minimal override setIn(0) end end local function updateTempEnergyPID() -- Temperature PID driving target energy delta local tDelta = setTemp - Temp() local eP1 = tDelta * tempCoe[1] table.insert(tempDeltaList, tDelta) if temp_it >= 3 and #tempDeltaList >= 2 then temp_eI = sum(tempDeltaList) * tempCoe[2] local eD1 = (tDelta - (tempDeltaList[#tempDeltaList - 1] or tempDeltaList[#tempDeltaList])) * tempCoe[3] -- eT1 total temperature PID output local eT1 = eP1 + temp_eI + eD1 local eDelta = eT1 * 100 -- now energy PID (use eDelta as input) local eP2 = eDelta * energyCoe[1] if eP2 + eV() >= 0 then table.insert(energyDeltaList, eDelta) if energy_it >= 3 and #energyDeltaList >= 2 then energy_eI = sum(energyDeltaList) * energyCoe[2] local eD2 = (eDelta - (energyDeltaList[#energyDeltaList - 1] or energyDeltaList[#energyDeltaList])) * energyCoe[3] local eT2 = eP2 + energy_eI + eD2 setOut(eV() + eT2) else -- still use proportional-only setOut(eV() + eP2) end end -- trim histories if #tempDeltaList > intgTime then while #tempDeltaList > intgTime do table.remove(tempDeltaList, 1) end end if #energyDeltaList > intgTime then while #energyDeltaList > intgTime do table.remove(energyDeltaList, 1) end end else -- initial accumulation: still set baseline outputs temp_it = math.min(temp_it + 1, intgTime + 1) energy_it = math.min(energy_it + 1, intgTime + 1) -- set conservative out to current generation (no extra) setOut(eV()) end end ------------------------------------------------------------------------------- -- GUI drawing (monitor proportional to 3x2) ------------------------------------------------------------------------------- local function initMonitorLayout() if not monitor then return end local ok, w, h = pcall(monitor.getSize, monitor) if ok and w and h then mon_w, mon_h = w, h else -- fallback mon_w, mon_h = 51, 19 end seg_w = math.floor(mon_w / 3) seg_h = math.floor(mon_h / 2) end local function monClear() if not monitor then return end pcall(monitor.clear, monitor) pcall(monitor.setCursorPos, monitor, 1, 1) end local function drawCentered(x, y, text) if not monitor then return end local txt = tostring(text) local len = #txt local col = math.max(1, math.floor((seg_w - len) / 2)) pcall(monitor.setCursorPos, monitor, (x - 1) * seg_w + col, (y - 1) * seg_h + 1) pcall(monitor.write, monitor, txt) end local function drawGUI() if not monitor then return end monClear() -- Title pcall(monitor.setCursorPos, monitor, 2, 1) pcall(monitor.write, monitor, "Draconic Reactor Control") -- Status region (left-top segment) local st = ("State: %s"):format(state:upper()) pcall(monitor.setCursorPos, monitor, 1, 3) pcall(monitor.write, monitor, st) -- Data region (middle-top) pcall(monitor.setCursorPos, monitor, seg_w + 2, 3) local fRate = FieldRate() * 100 local eRate = EnergyRate() * 100 local fuelPct = (1 - FuelRate()) * 100 pcall(monitor.write, monitor, ("Field: %.2f%%"):format(fRate)) pcall(monitor.setCursorPos, monitor, seg_w + 2, 4) pcall(monitor.write, monitor, ("Energy: %.2f%%"):format(eRate)) pcall(monitor.setCursorPos, monitor, seg_w + 2, 5) pcall(monitor.write, monitor, ("Fuel Left: %.2f%%"):format(fuelPct)) -- Temp region (right-top) pcall(monitor.setCursorPos, monitor, 2 * seg_w + 2, 3) pcall(monitor.write, monitor, ("TEMP: %d C"):format(Temp())) pcall(monitor.setCursorPos, monitor, 2 * seg_w + 2, 4) pcall(monitor.write, monitor, ("Out: %.2f RF/t"):format(curOut - curIn)) -- Center: big Field percent pcall(monitor.setCursorPos, monitor, seg_w + 2, seg_h - 2) pcall(monitor.write, monitor, ("Field Strength: %.2f%%"):format(FieldRate() * 100)) -- Bottom area: button local btnY = mon_h - 2 pcall(monitor.setCursorPos, monitor, 2, btnY) local btnText = (running and "[ STOP REACTOR ]" or "[ START REACTOR ]") pcall(monitor.write, monitor, btnText) -- If error state, show warning if state == "error" then pcall(monitor.setCursorPos, monitor, 2, btnY - 2) pcall(monitor.write, monitor, "!!! ERROR / SAFETY STOPPED !!!") end end ------------------------------------------------------------------------------- -- Touch handling: check if touch inside button area, toggle start/stop -- Uses proportional layout (bottom-left segment area) ------------------------------------------------------------------------------- local function handleMonitorTouch(x, y) if not monitor or not mon_w or not mon_h then return end local btnY = mon_h - 2 -- simplify: if y >= btnY and x between 1..mon_w, toggle if y >= btnY then if state ~= "error" then if running then -- stop reactor control: stop PIDs, try to stop reactor running = false -- stop reactor and zero outputs stopReactor() setOut(0) setIn(64000000) else running = true -- attempt initialize/attach initializeReactor() end else -- if in error, clicking does nothing (could be extended to reset) end end end ------------------------------------------------------------------------------- -- Emergency supervise (single-step check inside main loop) ------------------------------------------------------------------------------- local function emergencyCheck() if state == "active" and (Temp() >= MAX_TEMP or FieldRate() <= MIN_FIELD_RATE) then -- immediate protective actions setOut(0) setIn(64000000) pcall(stopReactor) state = "error" running = false end end ------------------------------------------------------------------------------- -- State machine handlers ------------------------------------------------------------------------------- local function handleIdle() -- waiting for peripherals detectPeripherals() local ok, msg = checkAPIs() if ok then state = "check" else state = "idle" end end local function handleCheck() local ok, msg = checkAPIs() if ok then state = "attach" else state = "idle" end end local function handleAttach() -- try to attach to existing reactor state (charge, activate if needed) -- keep previous running flag if initializeReactor() then -- if reactor is running already, accept control if status() == "running" then state = "active" running = true else -- if not running but charged, go to charge and then active state = "charge" end else -- initialization failed, wait a bit and retry state = "charge" end end local function handleCharge() -- charge until thresholds reached then try activate chargeReactor() setOut(0) setIn(64000000) local t = Temp() local f = Field() local e = Energy() if t >= 2000 and f >= 0.49 and e >= 0.49 then startReactor() if status() == "running" then state = "active" running = true else -- keep charging / waiting state = "charge" end else state = "charge" end end local function handleActive() emergencyCheck() if state == "error" then return end -- when running flag false, interpret as user stop if not running then -- stop PIDs and attempt to stop reactor stopReactor() state = "idle" return end -- update PID based on tick counters handled in main loop end local function handleError() -- show error and keep outputs safe setOut(0) setIn(64000000) -- wait until user reconnects peripherals and manually restart by clicking Start end ------------------------------------------------------------------------------- -- MAIN loop: uses os.startTimer + os.pullEvent for precise tick & non-blocking touch ------------------------------------------------------------------------------- local function main() -- initial detection detectPeripherals() initMonitorLayout() monClear() if monitor then pcall(monitor.setCursorPos, monitor, 1, 1) pcall(monitor.write, monitor, "Detecting peripherals...") end -- start a repeating timer for ticks local timerId = os.startTimer(TICK) while true do local ev = { os.pullEvent() } if ev[1] == "timer" and ev[2] == timerId then -- schedule next tick timerId = os.startTimer(TICK) tickCounter = tickCounter + 1 fieldCounter = fieldCounter + 1 tempCounter = tempCounter + 1 -- refresh detection occasionally (every 1s) if tickCounter % math.floor(1 / TICK) == 0 then detectPeripherals() initMonitorLayout() end -- run state machine dispatcher if state == "idle" then handleIdle() elseif state == "check" then handleCheck() elseif state == "attach" then handleAttach() elseif state == "charge" then handleCharge() elseif state == "active" then handleActive() elseif state == "error" then handleError() end -- PID updates on their own intervals (only if running and state active) if running and state == "active" then if fieldCounter >= FIELD_INTERVAL then updateFieldPID() fieldCounter = 0 end if tempCounter >= TEMP_INTERVAL then updateTempEnergyPID() tempCounter = 0 end -- avgOut sample (simple) avgOut = curOut - curIn end -- emergency check each tick emergencyCheck() -- draw GUI each tick (cheap) pcall(drawGUI) elseif ev[1] == "monitor_touch" then -- monitor touch event: params vary but typically (side, x, y) local side = ev[2] local tx = ev[3] local ty = ev[4] -- ensure event belongs to our monitor (if monitor.name matches 'side') if monitor and peripheralNames.monitor ~= "" then if side == peripheralNames.monitor then handleMonitorTouch(tx, ty) end else -- if monitor auto-detected, we cannot get side easily; still handle by touch handleMonitorTouch(tx, ty) end elseif ev[1] == "terminate" then -- user requested stop; cleanup setOut(0) setIn(64000000) stopReactor() break else -- ignore other events end end end ------------------------------------------------------------------------------- -- Entry point ------------------------------------------------------------------------------- local ok, msg = pcall(main) if not ok then -- best effort safe shutdown pcall(setOut, 0) pcall(setIn, 64000000) pcall(stopReactor) -- print error print("Script terminated with error:", msg) end
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)