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

Attempt To Index ? (A Nil Value)

Started by n1ghtk1ng, 21 July 2013 - 01:55 PM
n1ghtk1ng #1
Posted 21 July 2013 - 03:55 PM
I've been getting this error: button:63: attempt to index ?(a nil value)
I know what the error is saying/what it means(as in I know that it's trying to like 'call' something that is nil/null/isn't there) but I have no idea why it's doing that.
If you need me to clarify more, just ask. Also, I'm using DireWolf20's API.

API: http://pastebin.com/EBMLAVjY
My Code: http://pastebin.com/YAzs246K
Kingdaro #2
Posted 21 July 2013 - 04:20 PM
You never call your FarmButtons() function, and therefore, the button "Cow Farm" doesn't exist when you try to toggle it.
n1ghtk1ng #3
Posted 21 July 2013 - 04:35 PM
You never call your FarmButtons() function, and therefore, the button "Cow Farm" doesn't exist when you try to toggle it.

Yes I do(if that sounds mean, I'm not trying to be), it is ran in the function "FarmMenu", which is ran when the button,"Farms Menu" is clicked.
Yevano #4
Posted 21 July 2013 - 05:08 PM
It is called, but not before you attempt to toggle the button on line 33 of your code.
albrat #5
Posted 21 July 2013 - 05:45 PM
Your code will not work with the button API. Full stop.

The action you are trying to perform is State saving. I can make this code work to restore the states but the button will allways be defaulted to false. The button API (direwolf20's) pre programs all button states to false when you load the table.

You need to re-write the button API to handle state forwarding. Which means a complete re-write of the code you have written as well. (see your previous topic for the more complex solutions to this)
http://www.computercraft.info/forums2/index.php?/topic/14144-is-it-possible-to-write-a-boolean-to-a-file-instead-of-a-string/ < link to help you find the old topic.

This setTable makes the defaulted button setting to false on table loading. The API only handles One table. If you load a new table the buttons default to False.

function setTable(name, func, xmin, xmax, ymin, ymax)
   button[name] = {}
   button[name]["func"] = func
   button[name]["active"] = false
   button[name]["xmin"] = xmin
   button[name]["ymin"] = ymin
   button[name]["xmax"] = xmax
   button[name]["ymax"] = ymax
end
you would have to edit the API to receive a "active" varible. which means you would have to store every menu and button table somewhere.

Kingdaro is Perfectly correct in that "Cow Farm" does not exist when you call it.
This is because the button API only loads one table at a time. Your default starting table is the first menu ( which has "farm Menu", "test2","test3","test4" ) and that menu contains no references to "Cow Farm"… Untill you physically press the "Farm Menu" button the information that loads into the table and makes "Cow Farm" active is not loaded.

the CowBool() is the problem you need to write this code to make it work…

   mon.clear()
   MainButtons()
   button.heading("N1ght Network")
   FarmMenu()
   sleep(1)
   CowFarm()
   button.screen()
   sleep(1)
   back()
this basically simulates you pressing the buttons, sets the cable to on. But… it also does not make the button appear green next time you go into the farm menu, because (starting to sound like a broken record here) the code does not support state saving. The button API can not do what you want it to do as it is..
n1ghtk1ng #6
Posted 21 July 2013 - 06:18 PM
Your code will not work with the button API. Full stop.

The action you are trying to perform is State saving. I can make this code work to restore the states but the button will allways be defaulted to false. The button API (direwolf20's) pre programs all button states to false when you load the table.

You need to re-write the button API to handle state forwarding. Which means a complete re-write of the code you have written as well. (see your previous topic for the more complex solutions to this)
http://www.computerc...ad-of-a-string/ < link to help you find the old topic.

This setTable makes the defaulted button setting to false on table loading. The API only handles One table. If you load a new table the buttons default to False.

function setTable(name, func, xmin, xmax, ymin, ymax)
   button[name] = {}
   button[name]["func"] = func
   button[name]["active"] = false
   button[name]["xmin"] = xmin
   button[name]["ymin"] = ymin
   button[name]["xmax"] = xmax
   button[name]["ymax"] = ymax
end
you would have to edit the API to receive a "active" varible. which means you would have to store every menu and button table somewhere.

Kingdaro is Perfectly correct in that "Cow Farm" does not exist when you call it.
This is because the button API only loads one table at a time. Your default starting table is the first menu ( which has "farm Menu", "test2","test3","test4" ) and that menu contains no references to "Cow Farm"… Untill you physically press the "Farm Menu" button the information that loads into the table and makes "Cow Farm" active is not loaded.

the CowBool() is the problem you need to write this code to make it work…

   mon.clear()
   MainButtons()
   button.heading("N1ght Network")
   FarmMenu()
   sleep(1)
   CowFarm()
   button.screen()
   sleep(1)
   back()
this basically simulates you pressing the buttons, sets the cable to on. But… it also does not make the button appear green next time you go into the farm menu, because (starting to sound like a broken record here) the code does not support state saving. The button API can not do what you want it to do as it is..

Thank you VERY MUCH for making me realize that I have to edit the API all together. That has helped a bunch. Do you suggest that I restart my code all together? Unless you have an easy way to add what you suggested to my code.
Also, when the code you suggested forgot the

os.loadAPI("button")
(which is no big deal) and when you ran FarmMenu it acted as I actually pressed the button and opened the program(but I think I have a way of fixing that)
Thanks a bunch!
albrat #7
Posted 21 July 2013 - 06:58 PM
I just sat here working on a complete new idea.

I abandoned all ideas of adjusting tables, But I found a way to save states accross reloads. (even menu changes)…

Here is the new Button API code

mon = peripheral.wrap("right")
mon.setTextScale(1)
mon.setTextColor(colors.white)
local button={}
mon.setBackgroundColor(colors.black)
function clearTable()
   button = {}
   mon.clear()
end
						 
function setTable(name, func, xmin, xmax, ymin, ymax, state)
   button[name] = {}
   button[name]["func"] = func
   button[name]["active"] = state or false
   button[name]["xmin"] = xmin
   button[name]["ymin"] = ymin
   button[name]["xmax"] = xmax
   button[name]["ymax"] = ymax
end
function funcName()
   print("You clicked buttonText")
end
		 
function fillTable()
   setTable("ButtonText", funcName, 5, 25, 4, 8)
end  
function fill(text, color, bData)
   mon.setBackgroundColor(color)
   local yspot = math.floor((bData["ymin"] + bData["ymax"]) /2)
   local xspot = math.floor((bData["xmax"] - bData["xmin"] - string.len(text)) /2) +1
   for j = bData["ymin"], bData["ymax"] do
		  mon.setCursorPos(bData["xmin"], j)
		  if j == yspot then
				 for k = 0, bData["xmax"] - bData["xmin"] - string.len(text) +1 do
				    if k == xspot then
						   mon.write(text)
				    else
						   mon.write(" ")
				    end
				 end
		  else
				 for i = bData["xmin"], bData["xmax"] do
				    mon.write(" ")
				 end
		  end
   end
   mon.setBackgroundColor(colors.black)
end
	   
function screen()
   local currColor
   for name,data in pairs(button) do
		  local on = data["active"]
		  if on == true then currColor = colors.lime else currColor = colors.red end
		  fill(name, currColor, data)
   end
end
function toggleButton(name)
   button[name]["active"] = not button[name]["active"]
   screen()
end  
function flash(name)
   toggleButton(name)
   screen()
   sleep(0.15)
   toggleButton(name)
   screen()
end
																					   
function checkxy(x, y)
   for name, data in pairs(button) do
		  if y>=data["ymin"] and  y <= data["ymax"] then
				 if x>=data["xmin"] and x<= data["xmax"] then
				    data["func"]()
				    return true
				    --data["active"] = not data["active"]
				    --print(name)
				 end
		  end
   end
   return false
end
	   
function heading(text)
   w, h = mon.getSize()
   mon.setCursorPos(math.floor((w-string.len(text))/2+1), 1)
   mon.write(text)
end
	   
function label(w, h, text)
   mon.setCursorPos(w, h)
   mon.write(text)
end

Here is the new Menu Code.


    os.loadAPI("button")
    --[[ VARIABLES ]]--
	
    local mon = peripheral.wrap("right")
    local CowBool = "false"
	
    --[[ FUNCTIONS ]]--

    function FarmButtons()
		    button.setTable("Cow Farm", CowFarm, 10, 20, 3, 5, CowBool) -- pass new var to api.
		    button.setTable("Blaze Farm", BlazeFarm, 22, 32, 3, 5)
		    button.setTable("Back", back, 10, 17, 15, 17)
		    button.screen()
    end
	
    function MainButtons()
		    button.setTable("Farm Menu", FarmMenu, 10, 20, 3, 5)
		    button.setTable("Test2", main2, 22, 32, 3, 5)
		    button.setTable("Test3", main3, 10, 20, 8, 10)
		    button.setTable("Test4", main4, 22, 32, 8, 10)
		    button.screen()
    end
	
    function getClick()
		    event, side, x, y = os.pullEvent("monitor_touch")
		    button.checkxy(x,y)
    end
    function CheckCowBool()
		    if fs.exists("buttonstates") then  -- File exists catch
	 local filer = fs.open("buttonstates", "r")
			  ReadCow = filer.readLine()
			  CowBool = tostring(ReadCow) == "true"
			  filer.close()
			  if ReadCow == "true" then
	    rs.setBundledOutput("bottom", colors.white)
			  end
   end
    end
	
	
    function FarmMenu() --Clears the past(main) menu and runs/displays the "Farms" menu
		    button.flash("Farm Menu")
		    sleep(0.1)
   button.clearTable()
		    FarmButtons()
		    button.heading("Farms")
    end
	
    function main2()
	  sleep(0.8)	  --Do Stuff
    end
	
    function main3()
	  sleep(0.8)
		    --Do Stuff
    end
	
    function main4()
	  sleep(0.8)
		    --Do stuff
    end
	
    function back()
		    button.flash("Back")
		    button.clearTable()
		    MainButtons()
		    button.heading("N1ght Network")
    end
	
    function CowFarm()
		   
   local CowBool = not CowBool  -- why do 5 line if's, if one line not works.
		    local filew = fs.open("buttonstates", "w")
		    filew.writeLine(CowBool)
		    filew.close()
		  
		    button.toggleButton("Cow Farm")
   if CowBool then
		    rs.setBundledOutput("bottom", rs.getBundledOutput("bottom")+colors.white)
   else
   rs.setBundledOutput("bottom", rs.getBundledOutput("bottom")-colors.white)
   end
    end
	
    function BlazeFarm()
	   sleep(0.8)	 --Toggle the BlazeFarm
    end
    --[[ MAIN ]]--
CheckCowBool()
    mon.clear()
    MainButtons()
    button.heading("N1ght Network")
    while true do
		    getClick()
    end

I just spent the last hour making it work as it says on the tin. :D/>

(to carry a new state to another button you need a new varible and read from file.) then when the setTable( … ) is called just add the boolean state caller to the end of the table eg. CowBool like I already did. (the table insert will handle nil and true values, the default value being false).

Have fun :)/>
n1ghtk1ng #8
Posted 21 July 2013 - 10:13 PM
I just sat here working on a complete new idea.

I abandoned all ideas of adjusting tables, But I found a way to save states accross reloads. (even menu changes)…

Here is the new Button API code

mon = peripheral.wrap("right")
mon.setTextScale(1)
mon.setTextColor(colors.white)
local button={}
mon.setBackgroundColor(colors.black)
function clearTable()
   button = {}
   mon.clear()
end
						
function setTable(name, func, xmin, xmax, ymin, ymax, state)
   button[name] = {}
   button[name]["func"] = func
   button[name]["active"] = state or false
   button[name]["xmin"] = xmin
   button[name]["ymin"] = ymin
   button[name]["xmax"] = xmax
   button[name]["ymax"] = ymax
end
function funcName()
   print("You clicked buttonText")
end
		
function fillTable()
   setTable("ButtonText", funcName, 5, 25, 4, 8)
end  
function fill(text, color, bData)
   mon.setBackgroundColor(color)
   local yspot = math.floor((bData["ymin"] + bData["ymax"]) /2)
   local xspot = math.floor((bData["xmax"] - bData["xmin"] - string.len(text)) /2) +1
   for j = bData["ymin"], bData["ymax"] do
		  mon.setCursorPos(bData["xmin"], j)
		  if j == yspot then
				 for k = 0, bData["xmax"] - bData["xmin"] - string.len(text) +1 do
					if k == xspot then
						   mon.write(text)
					else
						   mon.write(" ")
					end
				 end
		  else
				 for i = bData["xmin"], bData["xmax"] do
					mon.write(" ")
				 end
		  end
   end
   mon.setBackgroundColor(colors.black)
end
	  
function screen()
   local currColor
   for name,data in pairs(button) do
		  local on = data["active"]
		  if on == true then currColor = colors.lime else currColor = colors.red end
		  fill(name, currColor, data)
   end
end
function toggleButton(name)
   button[name]["active"] = not button[name]["active"]
   screen()
end  
function flash(name)
   toggleButton(name)
   screen()
   sleep(0.15)
   toggleButton(name)
   screen()
end
																					  
function checkxy(x, y)
   for name, data in pairs(button) do
		  if y>=data["ymin"] and  y <= data["ymax"] then
				 if x>=data["xmin"] and x<= data["xmax"] then
					data["func"]()
					return true
					--data["active"] = not data["active"]
					--print(name)
				 end
		  end
   end
   return false
end
	  
function heading(text)
   w, h = mon.getSize()
   mon.setCursorPos(math.floor((w-string.len(text))/2+1), 1)
   mon.write(text)
end
	  
function label(w, h, text)
   mon.setCursorPos(w, h)
   mon.write(text)
end

Here is the new Menu Code.


	os.loadAPI("button")
	--[[ VARIABLES ]]--
	
	local mon = peripheral.wrap("right")
	local CowBool = "false"
	
	--[[ FUNCTIONS ]]--

	function FarmButtons()
			button.setTable("Cow Farm", CowFarm, 10, 20, 3, 5, CowBool) -- pass new var to api.
			button.setTable("Blaze Farm", BlazeFarm, 22, 32, 3, 5)
			button.setTable("Back", back, 10, 17, 15, 17)
			button.screen()
	end
	
	function MainButtons()
			button.setTable("Farm Menu", FarmMenu, 10, 20, 3, 5)
			button.setTable("Test2", main2, 22, 32, 3, 5)
			button.setTable("Test3", main3, 10, 20, 8, 10)
			button.setTable("Test4", main4, 22, 32, 8, 10)
			button.screen()
	end
	
	function getClick()
			event, side, x, y = os.pullEvent("monitor_touch")
			button.checkxy(x,y)
	end
	function CheckCowBool()
			if fs.exists("buttonstates") then  -- File exists catch
	 local filer = fs.open("buttonstates", "r")
			  ReadCow = filer.readLine()
			  CowBool = tostring(ReadCow) == "true"
			  filer.close()
			  if ReadCow == "true" then
		rs.setBundledOutput("bottom", colors.white)
			  end
   end
	end
	
	
	function FarmMenu() --Clears the past(main) menu and runs/displays the "Farms" menu
			button.flash("Farm Menu")
			sleep(0.1)
   button.clearTable()
			FarmButtons()
			button.heading("Farms")
	end
	
	function main2()
	  sleep(0.8)	  --Do Stuff
	end
	
	function main3()
	  sleep(0.8)
			--Do Stuff
	end
	
	function main4()
	  sleep(0.8)
			--Do stuff
	end
	
	function back()
			button.flash("Back")
			button.clearTable()
			MainButtons()
			button.heading("N1ght Network")
	end
	
	function CowFarm()
		  
   local CowBool = not CowBool  -- why do 5 line if's, if one line not works.
			local filew = fs.open("buttonstates", "w")
			filew.writeLine(CowBool)
			filew.close()
		  
			button.toggleButton("Cow Farm")
   if CowBool then
			rs.setBundledOutput("bottom", rs.getBundledOutput("bottom")+colors.white)
   else
   rs.setBundledOutput("bottom", rs.getBundledOutput("bottom")-colors.white)
   end
	end
	
	function BlazeFarm()
	   sleep(0.8)	 --Toggle the BlazeFarm
	end
	--[[ MAIN ]]--
CheckCowBool()
	mon.clear()
	MainButtons()
	button.heading("N1ght Network")
	while true do
			getClick()
	end

I just spent the last hour making it work as it says on the tin. :D/>

(to carry a new state to another button you need a new varible and read from file.) then when the setTable( … ) is called just add the boolean state caller to the end of the table eg. CowBool like I already did. (the table insert will handle nil and true values, the default value being false).

Have fun :)/>

Thank you for helping me so much, it's really helped clear things up, but I have a few bugs
1. The button's toggled state stays the same whenever I launch it
i.e. in the file "buttonstates", the bool is true, so when I launch it and open the menu, it's on. It will only toggle the button once(as in it will change the state in "buttonstates" to the opposite once, and won't toggle it again(It will change the color of the button for that instance, but when I close the Farm Menu it goes back to what it was). When I close the menu and open it again, whatever the state was when I launched it is what the color of the button/toggle will be
2. Might edit in later, I think I fixed it
albrat #9
Posted 22 July 2013 - 05:32 AM
hmm, That was strange. I had fully working code last night. but today it is doing strange things. Anyhows. I re-wrote the code again. reduced the API down, reduced down the Code for your buttons. and wrote it all again.

This time the state saving is surviving and updating accross menu's saves and crashes. (the CowBool is updated, saved and reloaded in the click of the Cow Farm button.)

CowBool is loaded at the start of the file. The state is initiated, redstone turned on if needed and then the states pass to the table correctly.

I am not sure about the turning on and off of individual bundled signals… (may need a function for activation and deactivation).

Here is the new and revised code for
BUTTON

mon = peripheral.wrap("top")
mon.setTextScale(1)
mon.setTextColor(colors.white)
local button={}
mon.setBackgroundColor(colors.black)

function clearTable()
   button = {}
   mon.clear()
end
						  
function setTable(name, func, xmin, xmax, ymin, ymax, state)
   button[name] = {}
   button[name]["func"] = func
   button[name]["active"] = (true == state) or false
   button[name]["xmin"] = xmin
   button[name]["ymin"] = ymin
   button[name]["xmax"] = xmax
   button[name]["ymax"] = ymax
end

function fill(text, color, bData)
   mon.setBackgroundColor(color)
   local yspot = math.floor((bData["ymin"] + bData["ymax"]) /2)
   local xspot = math.floor((bData["xmax"] - bData["xmin"] - string.len(text)) /2) +1
   for j = bData["ymin"], bData["ymax"] do
		  mon.setCursorPos(bData["xmin"], j)
		  if j == yspot then
				 for k = 0, bData["xmax"] - bData["xmin"] - string.len(text) +1 do
				    if k == xspot then
						   mon.write(text)
				    else
						   mon.write(" ")
				    end
				 end
		  else
				 for i = bData["xmin"], bData["xmax"] do
				    mon.write(" ")
				 end
		  end
   end
   mon.setBackgroundColor(colors.black)
end
	    
function screen()
   local currColor
   for name,data in pairs(button) do
		  local on = data["active"]
		  if on == true then currColor = colors.lime else currColor = colors.red end
		  fill(name, currColor, data)
   end
end

function toggleButton(name)
   button[name]["active"] = not button[name]["active"]
   screen()
   return (button[name]["active"])
end   

function flash(name)
   toggleButton(name)
   screen()
   sleep(0.15)
   toggleButton(name)
   screen()
end
																					    
function checkxy(x, y)
   for name, data in pairs(button) do
		  if y>=data["ymin"] and  y <= data["ymax"] then
				 if x>=data["xmin"] and x<= data["xmax"] then
				    data["func"]()
                    --data["active"] = not data["active"]
				    return true
				    
				    --print(name)
				 end
		  end
   end
   return false
end
	    
function heading(text)
   w, h = mon.getSize()
   mon.setCursorPos(math.floor((w-string.len(text))/2+1), 1)
   mon.write(text)
end
	    
function label(w, h, text)
   mon.setCursorPos(w, h)
   mon.write(text)
end

Here is the new and Revised code for
FarmMenu
    os.loadAPI("button")
    --[[ VARIABLES ]]--
     
    local mon = peripheral.wrap("top")
if fs.exists("buttonstates") then  -- File exists catch
  local filer = fs.open("buttonstates", "r")
  ReadCow = filer.readLine()
  CowBool = tostring(ReadCow) == "true"
  filer.close()
  if ReadCow == "true" then
   rs.setBundledOutput("bottom", colors.white)
  end
end
    --[[ FUNCTIONS ]]--
    
    function FarmButtons()
    mon.setCursorPos(1,5)
		    button.setTable("Cow Farm", CowFarm, 10, 20, 3, 5, CowBool) -- new state inc.
		    button.setTable("Blaze Farm", BlazeFarm, 22, 32, 3, 5)
		    button.setTable("Back", back, 10, 17, 15, 17)
		    button.screen()
    end
     
    function MainButtons()
		    button.setTable("Farm Menu", FarmMenu, 10, 20, 3, 5)
		    button.setTable("Test2", main2, 22, 32, 3, 5)
		    button.setTable("Test3", main3, 10, 20, 8, 10)
		    button.setTable("Test4", main4, 22, 32, 8, 10)
		    button.screen()
    end
     
    function getClick()
		    event, side, x, y = os.pullEvent("monitor_touch")
		    button.checkxy(x,y)
    end
     
    function FarmMenu() --Clears the past(main) menu and runs/displays the "Farms" menu
		    button.flash("Farm Menu")
		    sleep(0.1)
            button.clearTable()
		    FarmButtons()
		    button.heading("Farms")
    end
     
    function main2()
	  sleep(0.8)	  --Do Stuff
    end
     
    function main3()
	  sleep(0.8)
		    --Do Stuff
    end
     
    function main4()
	  sleep(0.8)
		    --Do stuff
    end
     
    function back()
		    button.flash("Back")
            --button.upDbool()
		    button.clearTable()
		    MainButtons()
		    button.heading("N1ght Network")
    end
     
    function CowFarm()
            CowBool = button.toggleButton("Cow Farm")
            local filew = fs.open("buttonstates", "w")
		    filew.writeLine(CowBool)
		    filew.close()
              local filer = fs.open("buttonstates", "r")
			  ReadCow = filer.readLine()
			  CowBool = tostring(ReadCow) == "true"
			  filer.close()
            if CowBool == true and rs.getBundledOutput("bottom") <= 1 then
		    rs.setBundledOutput("bottom", colors.white)
            else
            rs.setBundledOutput("bottom", 0)
            end
    end
     
    function BlazeFarm()
	   sleep(0.8)	 --Toggle the BlazeFarm
    end

    --[[ MAIN ]]--

    mon.clear()
    MainButtons()
    button.heading("N1ght Network")
    while true do
		    getClick()
    end