LUA Factory

Some time ago I wrote some skeleton code designed to make some simple widgets work properly in LUA. I then turned it into a factory control system. Since then I’ve advanced that to a control system using wireless modems and remote terminals so I can have one multi-screen factory computer control remote computers that do things like monitor reactors or control spawners etc. Here is the LUA that does this in my currently simple factory in case someone finds it useful.

Main Control Computer


-- Modem Port Numbers
local version = "0.2";
local myPort = 101; -- main factory control computer
local reactPort = 102; -- reactor main control
local spawnPort = 103; -- spawn room
local turbine0Port = 104; -- turbine 0

local running = true;
local modem = peripheral.wrap("right");
local mon = "";
local HB = 5;
local lastPowerTot = 0;

local buttons = {}
local window = {}
local dirty = false;

local ReactorCycles = 0;

function round(n,dec)
local shift = 10 ^ dec;
local ret = math.floor( n * shift ) / shift;

return ret;
end

----- Button Maintainence -----
function updateButtonCalc( button )
button["tlen"] = string.len( button["text"] )
button["height"] = button["pady"] * 2 + 1
button["width"] = button["padx"] * 2 + button["tlen"]
button["xend"] = button["origx"] + button["width"] - 1
button["yend"] = button["origy"] + button["height"] - 1
button["tX"] = button["origx"] + (math.floor( button["width"] / 2 ) - math.floor( button["tlen"] / 2 )) - 1 + button["padx"]
button["tY"] = button["origy"] + math.floor( button["height"] / 2 )
end

function addButton( name, text, func, state, origx, origy, padx, pady, fore, bact, binact )
buttons[name] = {}
buttons[name]["text"] = text
buttons[name]["state"] = state
buttons[name]["defstate"] = state
buttons[name]["func"] = func
buttons[name]["origx"] = origx
buttons[name]["origy"] = origy
buttons[name]["padx"] = padx
buttons[name]["pady"] = pady
buttons[name]["foreground"] = fore
buttons[name]["bact"] = bact
buttons[name]["binact"] = binact

updateButtonCalc(buttons[name])
end

function addSimpleButton( name, text, func, origx, origy )
addButton( name, text, func, false, origx, origy, 1, 1, colors.white, colors.cyan, colors.red )
end

function addInfoButton( name, text, origx, origy )
addButton( name, text, noop, false, origx, origy, 1, 0, colors.white, colors.black, colors.black)
end

function drawButton( bData )
local tLen = bData["tlen"]
local h = bData["height"]
local w = bData["width"]
local xend = bData["xend"]
local yend = bData["yend"]
local tY = bData["tY"]
local tX = bData["tX"]

if bData["state"] == true then
mon.setBackgroundColor( bData["bact"] )
else
mon.setBackgroundColor( bData["binact"] )
end
mon.setTextColor( bData["foreground"] )

for i = bData["origy"], yend do
mon.setCursorPos( bData["origx"], i )
if i == tY then
for j = bData["origx"], (xend - tLen + 1) do
if j == tX then
mon.write(bData["text"])
else
mon.write(" ")
end
end
else
for i = bData["origx"], xend do
mon.write(" ")
end
end
end
mon.setBackgroundColor( colors.black )
end

function drawButtons()
for name,data in pairs(buttons) do
drawButton( data )
end
end

function heading( yoff, text )
w, h = mon.getSize()
mon.setCursorPos( (w-string.len(text))/2+1, yoff )
mon.write( text )
end

function mainDrawHook()
heading(1, "Factory Control v"..version)
handleReactorAuto()
end

local drawHook = mainDrawHook;

local nextClear = 0
function drawScreen( )
if nextClear < os.clock() or dirty == true then mon.clear() nextClear = os.clock() + 10 dirty = false; end drawHook() drawButtons() end function toggleButton(name) buttons[name]["state"] = not buttons[name]["state"] end --[[ for alignment, horiz and vert can have 3 states: 0 no change 1 left/top 2 right/bottom 3 center ]]-- function alignButtonInBox( button, box, horiz, vert ) local x = box["o"]["x"] local y = box["o"]["y"] if horiz > 0 then
if horiz == 1 then
button["origx"] = x
elseif horiz == 2 then
x = (x + box["w"] - 1) - button["width"]
button["origx"] = x
elseif horiz == 3 then
x = x + (box["w"] / 2)
button["origx"] = x
end
end

if vert > 0 then
if vert == 1 then
button["origy"] = y
elseif vert == 2 then
y = (y + box["h"] - 1) - button["height"]
button["origy"] = y
elseif vert == 3 then
y = y + (box["h"] / 2)
button["origy"] = y
end
end

updateButtonCalc( button )

end

function checkButtonHit( x, y )
for name, data in pairs(buttons) do
if y >= data["origy"] and y <= data["yend"] then if x >= data["origx"] and x <= data["xend"] then data["func"](name) end end end end function shutdownButtons() for name, data in pairs(buttons) do if data["state"] ~= data["defstate"] then data["func"](name) end end end --- INIT --- function initMon(side) print("Initialize monitor.."); mon = peripheral.wrap(side) --mon.setTextScale(1) mon.setBackgroundColor(colors.black) mon.setTextColor(colors.white) window["o"] = {} window["o"]["x"] = 1 window["o"]["y"] = 1 window["w"], window["h"] = mon.getSize() print(" size="..window["w"].."x"..window["h"]); end -- open our modem function initModem() modem.open( myPort ); end function fetchValueFromRemote(remote, value) modem.transmit( remote, myPort, value); local timeout = os.startTimer(HB); local retval = "i/o err"; local waiting = 1; while waiting == 1 do local event, modemSide, senderchannel, replyPort, message, senderDistance = os.pullEvent(); if event == "modem_message" then waiting = 0; retval = message; elseif event == "timer" then waiting = 0; end end os.cancelTimer(timeout); return(retval); end function fetchValueFromReactor(value) local retval = fetchValueFromRemote( reactPort, value); return(retval); end function handleEvent() local timeout = os.startTimer(HB); local e, side, x, y = os.pullEvent(); if e == "monitor_touch" then checkButtonHit( x, y ) end os.cancelTimer(timeout) end function noop() print("noop"); end function toggleReactorAuto(name) local button = buttons[name] if button["state"] == false then button["state"] = true fetchValueFromReactor("keeppow"); else button["state"] = false fetchValueFromReactor("keeppow"); end end local nextRename = 0; function toggleCows(name) local button = buttons[name] if button["state"] == false then button["state"] = true fetchValueFromRemote(spawnPort, "tcow"); else button["state"] = false fetchValueFromRemote(spawnPort, "tcow"); end end function toggleBlazes(name) local button = buttons[name] if button["state"] == false then button["state"] = true fetchValueFromRemote(spawnPort, "tblaze"); else button["state"] = false fetchValueFromRemote(spawnPort, "tblaze"); end end function toggleGrinder(name) local button = buttons[name] if button["state"] == false then button["state"] = true fetchValueFromRemote(spawnPort, "toverride"); else button["state"] = false fetchValueFromRemote(spawnPort, "toverride"); end end function waitForRemote() mon.clear() for i=1,5 do heading(10, "Waiting for remote computers..."..tostring(5-i)); sleep(1); end end function queryRemoteButtonStates() if fetchValueFromRemote( spawnPort, "cows" ) ~= 0 then buttons["spawnCows"]["state"] = true; else buttons["spawnCows"]["state"] = false; end if fetchValueFromRemote( spawnPort, "blazes" ) ~= 0 then buttons["spawnBlazes"]["state"] = true; else buttons["spawnBlazes"]["state"] = false; end if fetchValueFromRemote( spawnPort, "override" ) ~= 0 then buttons["overrideGrind"]["state"] = true; else buttons["overrideGrind"]["state"] = false; end end function resetRemotes(name) fetchValueFromReactor("reboot"); fetchValueFromRemote(spawnPort, "reboot"); waitForRemote() queryRemoteButtonStates() end function reactorControl(name) mon.clear(); initReactButtons() drawHook = reactDrawHook; end function initMainButtons() HB = 5; buttons = {} -- add the reactor auto button and set its state addSimpleButton( "reactor", "Reactor Control", reactorControl, 1, 3 ) buttons["reactor"]["bact"] = colors.green; buttons["reactor"]["binact"] = colors.green; addSimpleButton( "spawnCows", "Spawn Cows", toggleCows, 1, 7 ); addSimpleButton( "spawnBlazes", "Spawn Blazes", toggleBlazes, 1, 11 ); addSimpleButton( "overrideGrind", "Grinder Ovrd ", toggleGrinder, 1, 15 ); -- second column -- local rbl = buttons["reactor"]["xend"] + 1; -- infoBox["o"] = {} -- infoBox["o"]["x"] = rbl; -- infoBox["o"]["y"] = 3; -- infoBox["w"] = 18; -- infoBox["h"] = 12; -- addInfoButton( "rinfo", "-Reactor Info-", rbl, 3 ) -- alignButtonInBox(buttons["rinfo"], infoBox, 3, 0 ) -- addInfoButton( "powlvl", "Power: "..fetchValueFromReactor("power"), rbl, 4 ) -- alignButtonInBox(buttons["powlvl"], infoBox, 3, 0 ) -- power button addSimpleButton( "power", "(|)", powerDown, 1, 3 ) buttons["power"]["binact"] = colors.purple alignButtonInBox( buttons["power"], window, 2, 0 ) addSimpleButton( "resetremote", "(8)", resetRemotes, 1, 7 ) buttons["resetremote"]["binact"] = colors.purple alignButtonInBox( buttons["resetremote"], window, 2, 0 ) queryRemoteButtonStates() end function backToMain() mon.clear() initMainButtons() drawHook = mainDrawHook; end ---------------- -- Reactor Control Screen ---------------- local turbineVitals = {} local reactorVitals = {} local rInfoBox = {} local trautorun = true; function drawRInfo() if nextRename < os.clock() then nextRename = os.clock() + 2; local pcolor = colors.white; if turbineVitals["power"] > 500000 then
pcolor = colors.green
elseif turbineVitals["power"] > 100000 then
pcolor = colors.yellow
else
pcolor = colors.red
end

local cX = rInfoBox["o"]["x"]
local cY = rInfoBox["o"]["y"]

-- heading --
mon.setTextColor(colors.cyan)
mon.setCursorPos(cX, cY)
mon.write("Turbine Info");

-- state
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Active: ");
if turbineVitals["active"] == true then
mon.setTextColor(colors.green);
mon.write("yes");
else
mon.setTextColor(colors.red);
mon.write("no");
end

-- coils (note - same line as state )
mon.setTextColor(colors.white);
mon.write(" Coils On: ");
if turbineVitals["coilson"] == true then
mon.setTextColor(colors.green);
mon.write("yes");
else
mon.setTextColor(colors.red);
mon.write("no");
end

-- power --
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Power Stored: ");
mon.setTextColor(pcolor);
mon.write(round(turbineVitals["power"], 2).." rf");

-- power --
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Generating: ");
mon.setTextColor(colors.pink);
mon.write(round(turbineVitals["elt"], 2).." rf/t");

-- Rotor --
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setTextColor(colors.white);
mon.setCursorPos(cX, cY);
mon.write("Rotor Speed: ");
local rcolor = colors.red;
if (turbineVitals["speed"] > 850 and turbineVitals["speed"] < 950) or (turbineVitals["speed"] > 1750 and turbineVitals["speed"] < 1850) then rcolor = colors.green; elseif (turbineVitals["speed"] > 750 and turbineVitals["speed"] < 1050) or (turbineVitals["speed"] > 1650 and turbineVitals["speed"] < 1950) then rcolor = colors.yellow; end mon.setTextColor(rcolor); mon.write(round(turbineVitals["speed"], 2).." rpm"); -- Reactor Info -- cY = cY + 2; cX = rInfoBox["o"]["x"]; mon.setTextColor(colors.cyan) mon.setCursorPos(cX, cY) mon.write("Reactor Info"); cY = cY + 1; cX = rInfoBox["o"]["x"]; mon.setTextColor(colors.white); mon.setCursorPos(cX, cY); mon.write("Active: "); if reactorVitals["active"] == true then mon.setTextColor(colors.green); mon.write("yes"); else mon.setTextColor(colors.red); mon.write("no"); end -- Fuel cY = cY + 1 cX = rInfoBox["o"]["x"]; mon.setTextColor(colors.white); mon.setCursorPos(cX, cY); mon.write("Fuel Eff: "); local fcolor = colors.red; if reactorVitals["fuelreact"] > 350 then
fcolor = colors.green;
elseif reactorVitals["fuelreact"] > 250 then
fcolor = colors.yellow;
elseif reactorVitals["fuelreact"] > 150 then
fcolor = colors.brown;
end
mon.setTextColor(fcolor);
mon.write(round(reactorVitals["fuelreact"], 2).."%");
mon.setTextColor(colors.white);
mon.write(" Burn: ");
mon.setTextColor(colors.yellow);
--local burn = math.floor(reactorVitals["fuellt"] * 1000) / 1000;
local burn = round(reactorVitals["fuellt"], 3);
mon.write(burn.." mb/t");

-- Rods
cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setCursorPos(cX, cY);
mon.setTextColor(colors.white);
mon.write("Rod Depth: ");
mon.setTextColor(colors.magenta);
mon.write( reactorVitals["rodlevel"].."%");

cY = cY + 1;
cX = rInfoBox["o"]["x"];
mon.setCursorPos(cX, cY);
mon.setTextColor(colors.white);
mon.write("Runtime: ");
mon.setTextColor(colors.lightBlue);
mon.write(round(ReactorCycles/5,2).." s");
end
end

function handleReactorAuto()
local result = fetchValueFromRemote(turbine0Port, "vitals");
turbineVitals = textutils.unserialize(result);

result = fetchValueFromReactor("vitals");
reactorVitals = textutils.unserialize(result);

if trautorun == true then
if turbineVitals["power"] < 10000 then ReactorCycles = ReactorCycles + 1; print("turning on power system"); fetchValueFromReactor("activate"); fetchValueFromRemote(turbine0Port, "activate"); fetchValueFromRemote(turbine0Port, "coilon"); elseif turbineVitals["power"] > 950000 then
print("turning off power system");
fetchValueFromReactor("deactivate");
fetchValueFromRemote(turbine0Port, "deactivate");
fetchValueFromRemote(turbine0Port, "coiloff");
end
end
end

function reactDrawHook()
heading(1, "Reactor Control v"..version);

handleReactorAuto()

resetReactButtons()
drawRInfo()
end

function tCoils(name)
if turbineVitals["coilson"] == true then
buttons["toggleCoils"]["text"] = "Coils";
buttons["toggleCoils"]["state"] = false;
fetchValueFromRemote(turbine0Port, "coiloff");
else
buttons["toggleCoils"]["text"] = "Coils";
buttons["toggleCoils"]["state"] = true;
fetchValueFromRemote(turbine0Port, "coilon");
end
updateButtonCalc(buttons["toggleCoils"])
dirty = true
end

function tReact(name)
if reactorVitals["active"] == true then
buttons[name]["state"] = false;
fetchValueFromReactor("deactivate");
else
buttons[name]["state"] = true;
fetchValueFromReactor("activate");
end
updateButtonCalc(buttons[name]);
dirty = true
end

function tTurb(name)
if turbineVitals["active"] == true then
buttons[name]["state"] = false;
fetchValueFromRemote(turbine0Port, "deactivate");
else
buttons[name]["state"] = true;
fetchValueFromRemote(turbine0Port, "activate");
end
updateButtonCalc(buttons[name]);
dirty = true
end

function resetReactButtons()
if turbineVitals["coilson"] == true then
buttons["toggleCoils"]["state"] = true;
updateButtonCalc(buttons["toggleCoils"])
else
buttons["toggleCoils"]["state"] = false;
updateButtonCalc(buttons["toggleCoils"])
end

if turbineVitals["active"] == true then
buttons["tturb"]["state"] = true;
updateButtonCalc(buttons["tturb"])
else
buttons["tturb"]["state"] = false;
updateButtonCalc(buttons["tturb"])
end

if reactorVitals["active"] == true then
buttons["treact"]["state"] = true;
updateButtonCalc(buttons["treact"]);
else
buttons["treact"]["state"] = false;
updateButtonCalc(buttons["treact"]);
end

if trautorun == true then
buttons["trauto"]["state"] = true;
updateButtonCalc(buttons["trauto"]);
else
buttons["trauto"]["state"] = false;
updateButtonCalc(buttons["trauto"]);
end

dirty = true
end

function tRAuto(name)
if trautorun == false then
print("toggling autorun to true");
trautorun = true
else
truatorun = false;
print("toggling autorun to false");
end
end

function initReactButtons()
HB = 5;
buttons = {}
addSimpleButton("back", "Back", backToMain, 1, 3);
buttons["back"]["bact"] = colors.green;
buttons["back"]["binact"] = colors.green;

-- initialize the vitals for the reactor so we can use them
local result = fetchValueFromRemote(turbine0Port, "vitals")
turbineVitals = textutils.unserialize(result)
result = fetchValueFromReactor("vitals");
reactorVitals = textutils.unserialize(result);

local rbl = buttons["back"]["xend"] + 4;
rInfoBox["o"] = {}
rInfoBox["o"]["x"] = rbl;
rInfoBox["o"]["y"] = 3;
rInfoBox["w"] = 18;
rInfoBox["h"] = 12;

addSimpleButton("toggleCoils", "Coils", tCoils, rbl, 1);
alignButtonInBox(buttons["toggleCoils"], window, 0, 2);
if turbineVitals["coilson"] == true then
buttons["toggleCoils"]["text"] = "Coils";
buttons["toggleCoils"]["state"] = true;
updateButtonCalc(buttons["toggleCoils"])
end

addSimpleButton("treact", "Reactor", tReact, buttons["toggleCoils"]["origx"] + buttons["toggleCoils"]["width"] + 2,
buttons["toggleCoils"]["origy"]);

addSimpleButton("tturb", "Turbine", tTurb, buttons["treact"]["origx"] + buttons["treact"]["width"] + 2,
buttons["treact"]["origy"]);

addSimpleButton("trauto", "Auto", tRAuto, buttons["tturb"]["origx"] + buttons["tturb"]["width"] + 2,
buttons["tturb"]["origy"]);

end

----------------
-- End Reactor Control
----------------

function powerDown()
running = 0;
end

local reactorPower = 0;
local ppt = 0;
local burnRate = 0;

-------
-- Startup/Runtime
-------

initMon("top")
initModem()
waitForRemote()
mon.clear()

initMainButtons()

while running == true
do
drawScreen()
handleEvent()
end

mon.clear();

Reactor Control

local myPort = 102;
local running = true;
local modem = peripheral.wrap("left");
local reactor = peripheral.wrap("BigReactors-Reactor_0");
local HB = 5;
local doReboot = 0;

modem.open( myPort );

while running == true
do
local timeout = os.startTimer(HB);
local event, modemSide, senderchannel,
replyPort, message, senderDistance = os.pullEvent();

local result;

-- values
local energyStored = reactor.getEnergyStored()
local energyPLT = reactor.getEnergyProducedLastTick()
local vitals = {};
vitals["active"] = reactor.getActive();
vitals["nrods"] = reactor.getNumberOfControlRods();
vitals["splt"] = reactor.getHotFluidProducedLastTick();
vitals["coolantvol"] = reactor.getCoolantAmount();
vitals["steamvol"] = reactor.getHotFluidAmount();
vitals["fuelreact"] = reactor.getFuelReactivity();
vitals["fuellt"] = reactor.getFuelConsumedLastTick();
vitals["rodlevel"] = reactor.getControlRodLevel(0);

if event == "modem_message" then
if message == "power" then
print("sending: "..energyStored)
result = energyStored;
elseif message == "ppt" then
result = energyPLT;
print("energy/tick: "..energyPLT)
elseif message == "quit" then
result = 0;
running = false;
elseif message == "keeppow" then
keepPowered = not keepPowered;
result = keepPowered;
local st = "on"
if keepPowered then
st = "off"
end
print("reactor-auto: "..st)
elseif message == "qkeeppow" then
result = keepPowered;
elseif message == "vitals" then
result = textutils.serialize(vitals);
elseif message == "reboot" then
doReboot = 1;
result = 0;
elseif message == "activate" then
reactor.setActive(true);
result = 0;
elseif message == "deactivate" then
reactor.setActive(false);
result = 0;
else
result = -1;
end
modem.transmit(replyPort, myPort, result);
end

os.cancelTimer(timeout);

if doReboot == 1 then
reactor.setActive(false);
os.reboot();
end
end

Turbine Control

local myPort = 104;
local running = true;
local modem = peripheral.wrap("left");
local reactor = peripheral.wrap("BigReactors-Turbine_0");
local HB = 5;
local doReboot = 0;

modem.open( myPort );

while running == true
do
local timeout = os.startTimer(HB);
local event, modemSide, senderchannel,
replyPort, message, senderDistance = os.pullEvent();

local result;

-- values
local energyStored = reactor.getEnergyStored()
local energyPLT = reactor.getEnergyProducedLastTick()
local vitals = {}
vitals["active"] = reactor.getActive()
vitals["power"] = reactor.getEnergyStored()
vitals["speed"] = reactor.getRotorSpeed()
vitals["rate"] = reactor.getFluidFlowRate()
vitals["elt"] = reactor.getEnergyProducedLastTick()
vitals["coilson"] = reactor.getInductorEngaged()

if event == "modem_message" then
if message == "power" then
print("sending: "..energyStored)
result = energyStored;
elseif message == "ppt" then
result = energyPLT;
print("energy/tick: "..energyPLT)
elseif message == "quit" then
result = 0;
running = false;
elseif message == "keeppow" then
keepPowered = not keepPowered;
result = keepPowered;
local st = "on"
if keepPowered then
st = "off"
end
print("reactor-auto: "..st)
elseif message == "vitals" then
result = textutils.serialize(vitals);
elseif message == "reboot" then
doReboot = 1;
result = 0;
elseif message == "coilon" then
reactor.setInductorEngaged(true);
elseif message == "coiloff" then
reactor.setInductorEngaged(false);
elseif message == "activate" then
reactor.setActive(true);
elseif message == "deactivate" then
reactor.setActive(false);
else
result = -1;
end
modem.transmit(replyPort, myPort, result);
end

os.cancelTimer(timeout);

if doReboot == 1 then
reactor.setActive(false);
os.reboot();
end
end

Spawner Control

-- Ports
local myPort = 103;

local running = true;
local modem = peripheral.wrap("right");
local HB = 60;

local cowSide = "bottom";
local blazeSide = "left";
local overrideSide = "back";
local doReboot = 0;

modem.open(myPort);

while running == true
do
local timeout = os.startTimer(HB);
local e, s, sChan, rPort, msg, sDist = os.pullEvent();
local tb;
local result = 0;

if e == "modem_message" then
if msg == "tcow" then
tb = not redstone.getOutput(cowSide);
redstone.setOutput(cowSide, tb);
print("set cows: "..tostring(tb));
elseif msg == "tblaze" then
tb = not redstone.getOutput(blazeSide);
redstone.setOutput(blazeSide, tb);
print("set blazes: "..tostring(tb));
elseif msg == "toverride" then
tb = not redstone.getOutput(overrideSide);
redstone.setOutput(overrideSide, tb);
print("set toverride: "..tostring(tb));
elseif msg == "cows" then
if redstone.getOutput(cowSide) then
result = 1;
end
elseif msg == "blazes" then
if redstone.getOutput(blazeSide) then
result = 1;
end
elseif msg == "override" then
if redstone.getOutput(overrideSide) then
result = 1;
end
elseif msg == "reboot" then
doReboot = 1
end

modem.transmit(rPort, myPort, result);
end

os.cancelTimer(timeout);

if doReboot == 1 then
os.reboot();
end
end

About

Agathezol is a gamer, programmer, father, husband, and musician. When he's not writing blog posts nobody reads he generally writes protocol stacks, network code, and core telecommunications software but has dabbled in game design, web programming, mobile device software, and desktop software.

Categories: Minecraft