This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
bechill's profile picture

befactory - Bundle controller for anything

Started by bechill, 20 May 2012 - 03:03 AM
bechill #1
Posted 20 May 2012 - 05:03 AM
A first program from someone 3 days into lua and computer craft, so go easy. Only first version.

[media]http://www.youtube.com/watch?v=Xwf6MT6pP2Y[/media]

I use the Redworks Addon installed via floppy on my terminals, this was designed in this environment and does depend on the redworks api for 1 function.

I have many future plans for this program, and tried to leave room to add them in later, so there are some unfinished peices lingering, once I've reached a point in my game that I need to impliment these other features I'll add them in.
.7z Download
Alternatively the code follows in the spoilers.

File: /mem/redworks/apis/be
Spoiler

--[[
	@author		Bechill
	@version	1.0
	@since		2012-05-19
]]

--[[name: clear
	Simple clear function so no need to rewrite constantly.
	@return nil
]]
clear = function()
	term.clear()
	term.setCursorPos(1,1)
end

home = function ()
	term.setCursorPos(1, select(2, term.getCursorPos()))
end

--[[name: fif
	Ternary function that can be called recursively with other functions
	@param condition	condition to test that resolves to a boolean
	@param if_true		return variable if condition true
	@param if_false		return variable if condition false
	@return mixed
]]
fif = function(condition, if_true, if_false)
  if condition then return if_true else return if_false end
end

--[[name: palignText
	Only required to provide string s, all other parameters have optional defaults

	Use: be.palignText{s="Test Title", a="RIGHT", sw="_/", lm=10, rm=term.getSize()-10}
	@param s string required to align
	@param a string alignement "LEFT" or "RIGHT" or "CENTER", defaults to "CENTER"
	@param lm integer to mark the left margin
	@param rm integer to mark the right margin
	@param sw string to surround text with
	@param sp integer to space lines with, useful for when included in a loop
	@param tr bool to set if text is truncated to fit within margins
]]
palignText = function(args)
	if type(args.s) ~= "string" then error("No string") end
	if args.a then
		if ((type(args.a) ~= "string") or not (args.a == "LEFT" or args.a == "RIGHT" or args.a == "CENTER")) then
			error("alignment should be "LEFT", "RIGHT", or "CENTER"")
		end
	end
	if args.sw then
		if type(args.s) ~= "string" then
			error("sw if supplied should be a string")
		end
	end
	_palignText (args.s,
				 args.a or "CENTER",
				 args.lm or 1,
				 args.rm or term.getSize(),
				 args.sw or nil,
				 args.sp or 1,
				 args.tr or false)
end

_palignText = function(str, align, leftMargin, rightMargin, surroundWith, spacing, truncate)
	local x, y = term.getCursorPos()
	local strlength = string.len(str)
	local space = rightMargin-leftMargin+1
	
	if surroundWith then
		local swl = string.len(surroundWith)
		term.setCursorPos(leftMargin, y)
		term.write(string.rep(surroundWith, math.modf(space/swl)))
		local remain = math.fmod(space, swl)
		if remain then
			term.write(string.sub(surroundWith, 1, remain))
		end
	end
	
	local doalign = {
		["LEFT"] = function ()
				term.setCursorPos(leftMargin, y)
			end,
		["RIGHT"] = function ()
				term.setCursorPos(rightMargin-strlength+1, y)
			end,
		["CENTER"] = function ()
				term.setCursorPos(leftMargin+space/2-strlength/2, y)
			end
	}

	doalign[align]()
	if truncate then
		term.write(string.sub(str, 1, space))
	else
		term.write(str)
	end
	if (spacing ~= 0) then
		term.setCursorPos(x, y+spacing)
	end
end

--[[name: drawBox
	@param x location for top left corner of box
	@param y location for top left corner of box
	@param h Height
	@param w Width
	@param fill Fill with character
	@param title Title on top of box
	@param titlea Title alignment "LEFT", "RIGHT", or "CENTER"
	@return nil  
]]--
drawBox = function (x, y, h, w, fill, title, titlea)
	term.setCursorPos(x, y)
	term.write("+"..string.rep("-", w-2).."+")
	if title then
		palignText{s=title, a=titlea, lm=x+1, rm=x+w-2, tr=true, sp=0, sw="-"}
	end
	for yy=y+1, y+h-2 do
		term.setCursorPos(x, yy)
		if fill then
			term.write("|"..string.rep(fill, w-2).."|")
		else
			term.write("|")
			term.setCursorPos(x+w-1, yy)
			term.write("|")
		end
	end
	term.setCursorPos(x,y+h-1)
	term.write("+"..string.rep("-", w-2).."+")
	term.setCursorPos(x,y)
end

menuBox = function (x, y, w, fill, title, titlea, tOptions, selected)
	drawBox(x, y, #tOptions+2, w, fill, title, titlea)
	if not selected or selected > #tOptions then selected = 1 end
	term.setCursorPos(x+1,y+1)
	for n=1, #tOptions do
		if (string.len(tOptions[n].name) > w-2) then
			opt = string.sub(tOptions[n].name, 1, w-2)
		else
			opt = tOptions[n].name
		end
		if (n == selected) then
			palignText{s=">"..opt.."<", lm=x+1, rm=x+w-1}
		else
			palignText{s=opt, lm=x+1, rm=x+w-1}
		end
	end
end
-- Internal persistence library

--[[ Provides ]]
-- persistence.store(path, ...): Stores arbitrary items to the file at the given path
-- persistence.load(path): Loads files that were previously stored with store and returns them

--[[ Limitations ]]
-- Does not export userdata, threads or most function values
-- Function export is not portable

--[[ License: MIT (see bottom) ]]

-- Private methods
local write, writeIndent, writers, refCount;

persistence =
{
	store = function (path, ...)
		local file, e;
		if type(path) == "string" then
			-- Path, open a file
			file, e = io.open(path, "w");
			if not file then
				return error(e);
			end
		else
			-- Just treat it as file
			file = path;
		end
		local n = select("#", ...);
		-- Count references
		local objRefCount = {}; -- Stores reference that will be exported
		for i = 1, n do
			refCount(objRefCount, (select(i,...)));
		end;
		-- Export Objects with more than one ref and assign name
		-- First, create empty tables for each
		local objRefNames = {};
		local objRefIdx = 0;
		file:write("-- Persistent Datan");
		file:write("local multiRefObjects = {n");
		for obj, count in pairs(objRefCount) do
			if count > 1 then
				objRefIdx = objRefIdx + 1;
				objRefNames[obj] = objRefIdx;
				file:write("{};"); -- table objRefIdx
			end;
		end;
		file:write("n} -- multiRefObjectsn");
		-- Then fill them (this requires all empty multiRefObjects to exist)
		for obj, idx in pairs(objRefNames) do
			for k, v in pairs(obj) do
				file:write("multiRefObjects["..idx.."][");
				write(file, k, 0, objRefNames);
				file:write("] = ");
				write(file, v, 0, objRefNames);
				file:write(";n");
			end;
		end;
		-- Create the remaining objects
		for i = 1, n do
			file:write("local ".."obj"..i.." = ");
			write(file, (select(i,...)), 0, objRefNames);
			file:write("n");
		end
		-- Return them
		if n > 0 then
			file:write("return obj1");
			for i = 2, n do
				file:write(" ,obj"..i);
			end;
			file:write("n");
		else
			file:write("returnn");
		end;
		file:close();
	end;

	load = function (path)
		local f, e = loadfile(path);
		if f then
			return f();
		else
			return nil, e;
		end;
	end;
}

-- Private methods

-- write thing (dispatcher)
write = function (file, item, level, objRefNames)
	writers[type(item)](file, item, level, objRefNames);
end;

-- write indent
writeIndent = function (file, level)
	for i = 1, level do
		file:write("t");
	end;
end;

-- recursively count references
refCount = function (objRefCount, item)
	-- only count reference types (tables)
	if type(item) == "table" then
		-- Increase ref count
		if objRefCount[item] then
			objRefCount[item] = objRefCount[item] + 1;
		else
			objRefCount[item] = 1;
			-- If first encounter, traverse
			for k, v in pairs(item) do
				refCount(objRefCount, k);
				refCount(objRefCount, v);
			end;
		end;
	end;
end;

-- Format items for the purpose of restoring
writers = {
	["nil"] = function (file, item)
			file:write("nil");
		end;
	["number"] = function (file, item)
			file:write(tostring(item));
		end;
	["string"] = function (file, item)
			file:write(string.format("%q", item));
		end;
	["boolean"] = function (file, item)
			if item then
				file:write("true");
			else
				file:write("false");
			end
		end;
	["table"] = function (file, item, level, objRefNames)
			local refIdx = objRefNames[item];
			if refIdx then
				-- Table with multiple references
				file:write("multiRefObjects["..refIdx.."]");
			else
				-- Single use table
				file:write("{n");
				for k, v in pairs(item) do
					writeIndent(file, level+1);
					file:write("[");
					write(file, k, level+1, objRefNames);
					file:write("] = ");
					write(file, v, level+1, objRefNames);
					file:write(";n");
				end
				writeIndent(file, level);
				file:write("}");
			end;
		end;
	["function"] = function (file, item)
			-- Does only work for "normal" functions, not those
			-- with upvalues or c functions
			local dInfo = debug.getinfo(item, "uS");
			if dInfo.nups > 0 then
				file:write("nil --[[functions with upvalue not supported]]");
			elseif dInfo.what ~= "Lua" then
				file:write("nil --[[non-lua function not supported]]");
			else
				local r, s = pcall(string.dump,item);
				if r then
					file:write(string.format("loadstring(%q)", s));
				else
					file:write("nil --[[function could not be dumped]]");
				end
			end
		end;
	["thread"] = function (file, item)
			file:write("nil --[[thread]]n");
		end;
	["userdata"] = function (file, item)
			file:write("nil --[[userdata]]n");
		end;
}

--[[
Copyright (c) 2010 Gerhard Roethlin

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]

File: /mem/redworks/programs/befactory
Spoiler

local revKeys = {["1"] = 1, ["2"] = 2, ["3"] = 3, ["4"] = 4, ["5"] = 5, ["6"] = 6,
				 ["7"] = 7, ["8"] = 8, ["9"] = 9, ["0"] = 10,["A"] = 11,["B"] = 12,
				 ["C"] = 13,["D"] = 14,["E"] = 15,["F"] = 16,["a"] = 11,["b"] = 12,
				 ["c"] = 13,["d"] = 14,["e"] = 15,["f"] = 16}

local autorunpath = "/mem/autorun/z.factorystart"
local settingspath = "/factory.settings"

local pat=be.palignText
local fif=be.fif
local _menuState, _componentToggle, _updateComponents, _componentToggle
local factory = {}
local _factoryOutSelect = 0

-- Copy redstone.getSides() into local table compatible with be.menuBox()
local sideOptions = {}
for k,v in ipairs(redstone.getSides()) do
	table.insert(sideOptions, k, {name = v})
end

_updateComponents = function ()
	c = 0
	for k in ipairs(factory.components) do
		if factory.components[k].monitored and factory.components[k].on then
			c = c + factory.components[k].color
		end
	end
	rs.setBundledOutput(factory.outdirection, c)
	be.persistence.store(settingspath, factory)
end

_componentSettings = function(c)
	be.clear()
	pat{s=" "..factory.componentname.." Settings ", a="LEFT", sw="_"}
	pat{s="----"..factory.components[c].name.." Settings ", a="LEFT", sw="-"}
	write("1) Name:	   "..factory.components[c].name.."n")
	write("2) Controlled: "..fif(factory.components[c].monitored, "Truen", "Falsen"))
	write("3) State:	  "..fif(factory.components[c].on, "Onn", "Offn"))
	write("4) Status:	 [Inactive in this version]n")
	term.setCursorPos(1, select(2, term.getSize())-1)
	pat{s="", sw="-"}
	term.write("[#] to change option, [D]one > ")
	_menuState = "COMPONENTMENU"
end

updateFactory = function()
	if factory.autostart and not fs.exists(autorunpath) then
		_writeAutostart()
	elseif not factory.autostart and fs.exists(autorunpath) then
		fs.delete(autorunpath)
	end
	_updateComponents()
end

_writeAutostart = function()
	file, e = io.open(autorunpath, "w");
	if not file then error(e) end

	file:write("shell.run("/mem/redworks/programs/befactory")")
	file:close();
end

_factoryOutChange = function()
	if _factoryOutSelect == 0 then
		for k,v in ipairs(sideOptions) do
			if factory.outdirection == v.name then
				_factoryOutSelect = k
				break
			end
		end
	end
	be.menuBox(term.getSize()/2-6, 5, 12, " ", "Output", nil, sideOptions, _factoryOutSelect)
end

--[[_factoryInChange = function()
end]]

displaymenuFactory = function()
	be.clear()
	pat{s=" "..factory.name.." Settings ", a="LEFT", sw="_"}
	pat{s="", sw="-"}
	write("1) Name:		   "..factory.name.."n")
	write("2) Component Name: "..factory.componentname.."n")
	write("3) Output Side:	"..factory.outdirection.."n")
	write("4) Input Side:	 "..factory.indirection.." [Inactive]n")
	write("5) Autostart:	  "..fif(factory.autostart, "True", "False"))

	
	term.setCursorPos(1, select(2, term.getSize())-1)
	pat{s="", sw="-"}
	term.write("[#] to change option, [M]enu > ")
	_menuState = "FACTORYMENU"
end

displaymenuToggle = function()
	be.clear()
	pat{s=" "..factory.componentname.." Settings ", a="LEFT", sw="_"}
	pat{s="", sw="-"}
	
	for k in ipairs(factory.components) do
		if (k < 9) then _lm=0 else _lm=math.modf(term.getSize()/2) end
		pat{s=factory.components[k].key..") "..factory.components[k].name, a="LEFT", lm=_lm+1, rm=_lm+20, sp=1}
		if k == 8 then term.setCursorPos(term.getSize()/2, 3) end
	end

	term.setCursorPos(1, select(2, term.getSize())-1)
	pat{s="", sw="-"}
	term.write("[#] to configure component, [M]enu > ")
	_menuState = "TOGGLEMENU"
end

displaymenuStatus = function ()
	be.clear()
	pat{s=" "..factory.name.." ", a="CENTER", sw="_"}
	pat{s=factory.componentname.." ", a="LEFT", sw="-", sp=1}

	local c=0
	for k in ipairs(factory.components) do
		if (factory.components[k].monitored) then
			c=c+1
			if (c < (select(2, term.getSize())-3)) then _lm=0 else _lm=math.modf(term.getSize()/2) end
			pat{s=factory.components[k].key..") ", a="LEFT", lm=_lm+1, rm=_lm+3, sp=0}
			pat{s=be.fif(factory.components[k].on,"ON","OFF"), a="RIGHT", lm=_lm+5, rm=_lm+6, sp=0}
			pat{s=factory.components[k].name, a="LEFT", lm=_lm+9, rm=_lm+24, sp=1, tr=true}
			if c == (select(2, term.getSize())-4) then term.setCursorPos(term.getSize()/2, 3) end
		end
	end

	term.setCursorPos(1, select(2, term.getSize())-1)
	pat{s="", sw="-"}
	term.write("[#] to toggle state, [M]enu > ")
	_menuState = "DISPLAYSTATUS"
end

mainmenuOptions = {
	[1] = { name = "Factory Settings", ret = function() displaymenuFactory() end },
	[2] = { name = "Toggle Components", ret = function() displaymenuToggle() end },
	[3] = { name = "Factory Status", ret = function() displaymenuStatus() end },
	[4] = { name = "Exit", ret = function() _menuState = "EXIT" end },
	["selected"] = 3
}
	
displaymenuMain = function ()
	be.menuBox(10, 5, 25, " ", "Menu", "CENTER", mainmenuOptions, mainmenuOptions["selected"])
	_menuState = "MAINMENU"
end

main = function()
	if fs.exists(settingspath) then
		factory = be.persistence.load(settingspath)
		_updateComponents()
	else
		factory.name = "Powerplant"
		factory.componentname = "Generator"
		factory.outdirection = "back"
		factory.indirection = "back"
		factory.autostart = false
		factory.components = {
		[1] = {key = "1", color = colors.white,		name = "Engine 1", monitored = true, on = false, status=nil},
		[2] = {key = "2", color = colors.orange,	name = "Engine 2", monitored = true, on = false, status=nil},
		[3] = {key = "3", color = colors.magenta,	name = "Engine 3", monitored = true, on = false, status=nil},
		[4] = {key = "4", color = colors.lightBlue,	name = "Engine 4", monitored = true, on = false, status=nil},
		[5] = {key = "5", color = colors.yellow,	 name = "Engine 5", monitored = true, on = false, status=nil},
		[6] = {key = "6", color = colors.lime,		 name = "Engine 6", monitored = true, on = false, status=nil},
		[7] = {key = "7", color = colors.pink,		 name = "Engine 7", monitored = true, on = false, status=nil},
		[8] = {key = "8", color = colors.gray,		 name = "Engine 8", monitored = true, on = false, status=nil},
		[9] = {key = "9", color = colors.lightGray,	name = "Engine 9", monitored = true, on = false, status=nil},
		[10] = {key = "0", color = colors.cyan,	 name = "Engine 10", monitored = true, on = false, status=nil},
		[11] = {key = "A", color = colors.purple,	 name = "Engine 11", monitored = true, on = false, status=nil},
		[12] = {key = "B", color = colors.blue,	 name = "Engine 12", monitored = true, on = false, status=nil},
		[13] = {key = "C", color = colors.brown,	 name = "Engine 13", monitored = true, on = false, status=nil},
		[14] = {key = "D", color = colors.green,	 name = "Engine 14", monitored = true, on = false, status=nil},
		[15] = {key = "E", color = colors.red,		 name = "Engine 15", monitored = true, on = false, status=nil},
		[16] = {key = "F", color = colors.black,	 name = "Engine 16", monitored = true, on = false, status=nil}
		}
		_updateComponents()
	end
	displaymenuStatus()

	local menuState = {
		["DISPLAYSTATUS"] = function ()
				if event == "char" then
					if revKeys[p1] and factory.components[revKeys[p1]].monitored then
						factory.components[revKeys[p1]].on = fif(factory.components[revKeys[p1]].on, false, true)
						_updateComponents()
						displaymenuStatus()
					elseif (p1 == "m" or p1 == "M") then
						displaymenuMain()
					end
				end
			end,
		["MAINMENU"] = function ()
				if event == "key" then
					if (p1 == 200) and (mainmenuOptions["selected"] > 1) then
						mainmenuOptions["selected"] = mainmenuOptions["selected"] - 1
						displaymenuMain()
					elseif (p1 == 208) and (mainmenuOptions["selected"] < #mainmenuOptions) then
						mainmenuOptions["selected"] = mainmenuOptions["selected"] + 1
						displaymenuMain()
					elseif (p1 == 28) or (p1 == 156)
						then mainmenuOptions[mainmenuOptions["selected"]].ret()
					end
				end
			end,
		["FACTORYMENU"] = function ()
				if event == "key" then
					if _factoryOutSelect ~= 0 then
						if (p1 == 200) and (_factoryOutSelect > 1) then
						_factoryOutSelect = _factoryOutSelect - 1
						_factoryOutChange()
						elseif (p1 == 208) and _factoryOutSelect < #sideOptions then
							_factoryOutSelect = _factoryOutSelect + 1
							_factoryOutChange()
						elseif ((p1 == 28) or (p1 == 156)) and _factoryOutSelect then
							factory.outdirection = sideOptions[_factoryOutSelect].name
							_factoryOutSelect = 0
							updateFactory()
							displaymenuFactory()
						end
					else
							if (p1 == 2) then -- "1"
							os.pullEvent("char") -- Discard char event
							factory.name = redworks.textBox(19,2,20,3,true,false,factory.name, false)
							updateFactory()
							displaymenuFactory()
						elseif (p1 == 3) then -- "2"
							os.pullEvent("char") -- Discard char event
							factory.componentname = redworks.textBox(19,3,20,3,true,false,factory.componentname, false)
							updateFactory()
							displaymenuFactory()
						elseif (p1 == 4) then -- "3"
							_factoryOutChange()
							updateFactory()
						-- elseif (p1 == 5) then -- "4"
							--_factoryInChange()
							--updateFactory()
						elseif (p1 == 6) then -- "5"
							factory.autostart = fif(factory.autostart, false, true)
							updateFactory()
							displaymenuFactory()
						elseif (p1 == 50) then -- "m"
							displaymenuMain()
						end
					end
				end
			end,
		["TOGGLEMENU"] = function ()
				if event == "char" then
					if revKeys[p1] then
						_currentComponent = revKeys[p1]
						_componentSettings(_currentComponent)
					elseif (p1 == "m" or p1 == "M") then
						displaymenuMain()
					end
				end
			end,
		["COMPONENTMENU"] = function ()
				if event == "char" then
					if (p1 == "1") then
						factory.components[_currentComponent].name =
							redworks.textBox(15,2,20,3,true,false,factory.components[_currentComponent].name, false)
						_updateComponents()
						_componentSettings(_currentComponent)
					elseif (p1 == "2") then
						factory.components[_currentComponent].monitored =
							fif(factory.components[_currentComponent].monitored, false, true)
						_updateComponents()
						_componentSettings(_currentComponent)
					elseif (p1 == "3") then
						factory.components[_currentComponent].on = fif(factory.components[_currentComponent].on, false, true)
						_updateComponents()
						_componentSettings(_currentComponent)
					elseif (p1 == "d" or p1 == "D") then
						displaymenuToggle()
					end
				end
			end
	}
	
	while _menuState ~= "EXIT" do
		event, p1 = os.pullEvent()
		menuState[_menuState]()
	end
	term.setCursorPos(1, select(2, term.getSize()))
	pat{s="Type "befactory" to restart.  Goodbye!", a="LEFT", sw=" "}
	print()
end

main()

If you use any of my code in a seperate project for fork this, credit would be nice. :P/>/>
FuzzyPurp #2
Posted 20 May 2012 - 08:44 AM
Nice work :P/>/>
Learning_inpaired #3
Posted 30 May 2012 - 11:59 PM
very noce :)/>/>
Learning_inpaired #4
Posted 31 May 2012 - 12:37 AM
factory:9: attempt to index ?(a nil value)
MysticT #5
Posted 31 May 2012 - 12:50 AM
factory:9: attempt to index ?(a nil value)
I think you didn't install the files correctly. You have to put the "be" file in "mem/redworks/apis" for this to work. And I think you have to reboot (or load the api) before using the program.