@dave1707, I fixed it again. Hopefully there are no more errors.
Same error.
Main:196: attempt to index a nil value (field ‘?’)
stack traceback:
Main:196: in function ‘receiveData’
Main:328: in function ‘draw’
if server_info["world_state"][clientState]["timestamp"] > server_info["serverTimestamp"] then
server_info["serverTimestamp"] = server_info["world_state"][clientState]["timestamp"]
end
@dave1707, change the code to this:
if server_info["world_state"][clientState]["serverTick"] > server_info["serverTimestamp"] then
server_info["serverTimestamp"] = server_info["world_state"][clientState]["serverTick"]
end
Same error.
Main:196: attempt to index a nil value (field ‘?’)
stack traceback:
Main:196: in function ‘receiveData’
Main:328: in function ‘draw’
Same error. I don’t think this is working. Maybe you should wait until you have access to multiple iPads so you can write and test your code and actually see what’s happening.
@dave1707, I was referencing a nil value and I didn’t know. Here’s the updated code:
function setup()
socket = require("socket")
parameter.action("Create Room", createRoom)
parameter.action("Join Room", function()
if other_ip then
joinRoom(other_ip, false)
else
parameter.text("IP", "", function(t)
other_ip = t
end)
end
end)
parameter.action("Find Room", function()
findRoom()
end)
client = socket.udp()
client:settimeout(0)
listener = socket.udp()
listener:settimeout(0)
-- We need to create a table that can hold information about our server, if you end up making one
server_info = {players = 0, clients = {}, world_state = {}, ids_taken = {}, serverTick = 0, serverTimestamp = 0}
-- We also need to create a table that can hold info that the client has
client_info = {serverIp = "", world_state = {}, serverTick = 0}
other_ip = nil
current_time = nil
current_room_count = 1
tick = 0
finding_room = false
server = false
joinedRoom = false
myPlayerServer = nil
myPlayerClient = nil
serverTick = 0
end
function createRoom()
-- We need to find your address, so we create a new UDP socket and assign it a random IP
local ip, port = getLocalIP()
client:setsockname("*", "14285")
local ip2, port2 = client:getsockname()
client:setoption("broadcast", true)
your_ip = ip2
your_port = port2
print("Room created!")
print("Connect to "..ip)
server = true
end
function joinRoom(ip, finding_room)
if server == false then
if ip ~= "*" then
if finding_room == false then
print("Attempting to join the room...")
end
-- The port for all the servers is the same, so we only need the IP address of the server
client:setpeername(ip, "14285")
client:send("connection confirmation")
current_time = os.time()
else
print("Address not allowed!")
end
else
print("You are already hosting a server!")
end
end
function findRoom()
if server == false then
print("Attempting to find a room...")
finding_room = true
local ip, port = getLocalIP()
local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
local success, err = pcall(function()
for z = 1, 255 do
client:setpeername(ip1..z, "14285")
client:send("requesting server info")
end
client:setpeername("*")
client:setsockname("*", "14285")
end)
if success == false then
-- The only error you could possibly get in this case is no internet connection, so we print it
print("No internet connection, please try again later!")
return
end
current_time = os.time()
else
print("You are already hosting a server!")
end
end
function sendMessage(msg)
if server == true then
for i,c in pairs(server_info["clients"]) do
client:sendto(tostring(msg), c.ip, c.port)
end
end
end
function leaveRoom()
-- We need to notify the server that we are leaving, so we send a packet to them
if server == false then
client:send("disconnection confirmation")
end
end
function resetParameters(params, args)
parameter.clear()
for i = 1, #params do
parameter.action(params[i], args[i])
end
end
function getLocalIP()
local udp = socket.udp()
udp:setpeername("192.167.188.122", "14285")
local ip, port = udp:getsockname()
udp:close()
return ip, port
end
function receiveData()
if server == true then
local msg, ip, port = client:receivefrom()
if msg ~= nil then
if msg == "connection confirmation" then
if server_info["players"] < 32 then
-- We can now add 1 more to our player count
server_info["players"] = server_info["players"] + 1
-- We now need to store the clients IP and port, so we can send them messages later
local uid = {ip = ip, port = port}
if not server_info["clients"][uid] then
table.insert(server_info["clients"], uid)
end
-- We now create a random ID for the connecting peer so we can differentiate between players on the server
local playerID = math.random(1, 100000)
if not server_info["ids_taken"][playerID] then
server_info["ids_taken"][playerID] = playerID
else
playerID = math.random(1, 100000)
end
client:sendto("valid confirmation"..playerID, ip, port)
-- We need to send the joining client our IP, so they can send messages to us later
local our_ip, our_port = getLocalIP()
client:sendto("server ip"..our_ip, ip, port)
-- We can now send everyone that has joined this room a packet telling them the amount of players in the room
for i,c in pairs(server_info["clients"]) do
client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
end
else
-- We don't have enough room for another player, so we send the peer a message telling them to disconnect
local reason = "Max players inside the room!"
client:sendto("invalid confirmation"..reason, ip, port)
end
elseif msg == "requesting server info" then
-- We need to find your address, so we create a new UDP socket and assign it a random IP
local our_ip, our_port = getLocalIP()
-- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
local uid = {ip = our_ip}
client:sendto("server info"..json.encode(uid), ip, port)
elseif msg and string.find(msg, "disconnection confirmation") then
-- We know that one of the clients connected to this server wants to leave, so we send a packet back telling them that we have been notified
client:sendto("notified of disconnection", ip, port)
local uid = {ip = ip, port = port}
local clientInfo
local clientState
for i = 1, #server_info["clients"] do
if server_info["clients"][i] == uid then
clientInfo = i
end
end
table.remove(server_info["clients"], clientInfo)
-- We also need to deduct the player count
server_info["players"] = server_info["players"] - 1
elseif msg and string.find(msg, "client state") then
-- A connected peer has sent their client state to us, so we update our world state accordingly
local clientState = json.decode(string.sub(msg, 13, #msg))
local hasClientState = false
local existingState = nil
for i,s in pairs(server_info["world_state"]) do
if s.playerName == clientState.playerName then
hasClientState = true
existingState = s
end
end
if hasClientState == true then
if clientState[“serverTick”] < server_info["serverTimestamp"] then
existingState.playerX = clientState.playerX
existingState.playerY = clientState.playerY
server_info["serverTimestamp"] = clientState.timestamp
end
else
if not server_info["world_state"][clientState] then
server_info["world_state"][clientState] = clientState
if server_info["world_state"][clientState]["serverTick"] < server_info["serverTimestamp"] then
server_info["serverTimestamp"] = server_info["world_state"][clientState]["serverTick"]
end
end
end
end
end
else
if finding_room == false then
local result = client:receive()
if result and string.find(result, "valid confirmation") then
-- We know that we successfully joined the room, and we can now receive messages from the server!
print("Successfully joined the room!")
joinedRoom = true
current_time = nil
parameter.clear()
-- We now create a button that allows the player to leave the room
local paramName = {"Leave Room"}
local paramArgs = {function() leaveRoom() end}
resetParameters(paramName, paramArgs)
-- We now find the player ID sent to us by the server and set it as our name
local playerID = string.sub(result, 19, #result)
local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2, timestamp = client_info["serverTic"]}
myPlayerClient = playerTab
elseif result and string.find(result, "server ip") then
-- Now that we have the servers IP address, we can message them later if we need to
local serverIp = string.sub(result, 10, #result)
client_info["serverIp"] = serverIp
elseif result and string.find(result, "player count") then
-- We now know the current amount of players in our room, for now at least
local count = string.sub(result, 13, #result)
print("Player count: "..count)
elseif result and string.find(result, "invalid confirmation") then
-- We can now see the reason for our invalid confirmation and display it
local reason = string.sub(result, 21, #result)
print("Could not join the room: "..reason)
current_time = nil
-- We can now also remove the address and port set for our peer
client:setpeername("*")
elseif result == "notified of disconnection" then
-- We know that we can now safely leave the server
client:setpeername("*")
joinedRoom = false
other_ip = nil
print("Successfully left the room!")
local paramNames = {"Create Room", "Join Room", "Find Room"}
local paramArgs = {createRoom, function()
if other_ip then
joinRoom(other_ip, false)
else
parameter.text("IP", "", function(t)
other_ip = t
end)
end
end, findRoom}
resetParameters(paramNames, paramArgs)
elseif result and string.find(result, "server tick") then
-- Now that we have received the server tick, we can properly update everything without any issues (hopefully)
local serverTick = string.sub(result, 12, #result)
client_info["serverTick"] = serverTick
elseif result == nil then
if current_time then
if finding_room == false then
if os.time() > current_time + 5 then
-- If we get no response back for 5 seconds or more, we know it didn't work
print("Failed to connect, please try again later")
current_time = nil
end
end
end
end
else
local result, ip, port = client:receivefrom()
if result and string.find(result, "server info") then
-- Now we can attempt to join the room with the given IP address
local tab = json.decode(string.sub(result, 12, #result))
if finding_room == true then
joinRoom(tab.ip, true)
finding_room = false
end
else
if finding_room == true then
if current_time and os.time() > current_time + 15 then
-- If we get no response back for 15 seconds or more, we know we couldn't find a room
print("Failed to find a room!")
current_time = nil
end
end
end
end
end
end
function updatePlayers()
-- We now update the position of our player puppets, so it looks like the players are being updated in realtime
for i,p in pairs(server_info["world_state"]) do
ellipse(p.playerX, p.playerY, 35)
end
-- We also have to send every player our current tick so everything is properly synchronized such as player positions
if server == false then
for i,c in pairs(server_info["clients"]) do
client:sendto("server tick"..server_info["serverTick"], c.ip, c.port)
end
end
end
function touched(touch)
if touch.state ~= ENDED and touch.state ~= CANCELLED then
if touch.state == BEGAN or touch.state == MOVING then
-- Here we send a packet to the server every frame, telling them our player position so it can properly update the whole game
if joinedRoom == true then
if myPlayerClient then
myPlayerClient.playerX = CurrentTouch.x
myPlayerClient.playerY = CurrentTouch.y
local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, serverTick = client_info["serverTick"]}
player:send("client state"..json.encode(playerTab))
end
end
end
end
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
updatePlayers()
receiveData()
if server == true then
server_info["serverTick"] = server_info["serverTick"] + 1
end
end
Main:196: attempt to index a nil value (field ‘?’)
stack traceback:
Main:196: in function ‘receiveData’
Main:328: in function ‘draw’
@dave1707, I don’t know why I keep spelling things wrong, but I fixed another spelling error. HOPEFULLY it works now.
Main:196: attempt to index a nil value (field ‘?’)
stack traceback:
Main:196: in function ‘receiveData’
Main:328: in function ‘draw’
if server_info["world_state"][clientState]["serverTick"] > server_info["serverTimestamp"] then
@dave1707, changed the code again. I know something won’t work because that what always happens, but yeah.
I’m now getting an error on line 188.
if clientState.timestamp > server_info["serverTimestamp"] then
I checked clientState.timestamp and it’s nil.
if clientState[“serverTick”] > server_info["serverTimestamp"] then
The above line, around 188, gives an error.
The “ ” around serverTick aren’t the correct “”. I had to correct them. Apparently you didn’t try to test anything or you would have run into that error.
Anyways, after I corrected the error and ran the code, a circle appeared on iPad 1, but didn’t move when I moved my finger around iPad 2.
@dave1707, I don’t know why the speech marks worked. I did fix the player not moving on iPad 1 (hopefully).
Line 188 is still giving an error. The circle still doesn’t move.
@dave1707, I took one of the earlier versions of this project (not too early, maybe two or three posts up from the bottom of this discussion) and added some more code. I also changed the code from having the old time stamp checking code (which was confusing and difficult) and changed it to a number which would increase every time you sent a packet to the server. The server would then check if the send number is greater than the send number which was held in the existing client state. Here’s the code:
function setup()
socket = require("socket")
parameter.action("Create Room", createRoom)
parameter.action("Join Room", function()
if other_ip then
joinRoom(other_ip, false)
else
parameter.text("IP", "", function(t)
other_ip = t
end)
end
end)
parameter.action("Find Room", function()
findRoom()
end)
client = socket.udp()
client:settimeout(0)
listener = socket.udp()
listener:settimeout(0)
-- We need to create a table that can hold information about our server, if you end up making one
server_info = {players = 0, clients = {}, world_state = {}, puppet_states = {}, ids_taken = {}}
-- We also need to create a table that can hold info that the client has
client_info = {serverIp = "", world_state = {}}
other_ip = nil
current_time = nil
current_room_count = 1
tick = 0
finding_room = false
server = false
joinedRoom = false
myPlayerServer = nil
myPlayerClient = nil
sendNumber = 0
end
function createRoom()
-- We need to find your address, so we create a new UDP socket and assign it a random IP
local ip, port = getLocalIP()
client:setsockname("*", "14285")
local ip2, port2 = client:getsockname()
client:setoption("broadcast", true)
your_ip = ip2
your_port = port2
print("Room created!")
print("Connect to "..ip)
server = true
end
function joinRoom(ip, finding_room)
if server == false then
if ip ~= "*" then
if finding_room == false then
print("Attempting to join the room...")
end
-- The port for all the servers is the same, so we only need the IP address of the server
client:setpeername(ip, "14285")
client:send("connection confirmation")
current_time = os.time()
else
print("Address not allowed!")
end
else
print("You are already hosting a server!")
end
end
function findRoom()
if server == false then
print("Attempting to find a room...")
finding_room = true
local ip, port = getLocalIP()
local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
local success, err = pcall(function()
for z = 1, 255 do
client:setpeername(ip1..z, "14285")
client:send("requesting server info")
end
client:setpeername("*")
client:setsockname("*", "14285")
end)
if success == false then
-- The only error you could possibly get in this case is no internet connection, so we print it
print("No internet connection, please try again later!")
return
end
current_time = os.time()
else
print("You are already hosting a server!")
end
end
function sendMessage(msg)
if server == true then
for i,c in pairs(server_info["clients"]) do
client:sendto(tostring(msg), c.ip, c.port)
end
end
end
function leaveRoom()
-- We need to notify the server that we are leaving, so we send a packet to them
player:send("disconnection confirmation")
end
function resetParameters(params, args)
parameter.clear()
for i = 1, #params do
parameter.action(params[i], args[i])
end
end
function getLocalIP()
local udp = socket.udp()
udp:setpeername("192.167.188.122", "14285")
local ip, port = udp:getsockname()
udp:close()
return ip, port
end
function receiveData()
if server == true then
local msg, ip, port = client:receivefrom()
if msg ~= nil then
if msg == "connection confirmation" then
if server_info["players"] < 32 then
-- We can now add 1 more to our player count
server_info["players"] = server_info["players"] + 1
-- We now need to store the clients IP and port, so we can send them messages later
local uid = {ip = ip, port = port}
if not server_info["clients"][uid] then
table.insert(server_info["clients"], uid)
end
-- We now create a random ID for the connecting peer so we can differentiate between players on the server
local playerID = math.random(1, 100000)
if not server_info["ids_taken"][playerID] then
server_info["ids_taken"][playerID] = playerID
else
playerID = math.random(1, 100000)
end
client:sendto("valid confirmation"..playerID, ip, port)
-- We need to send the joining client our IP, so they can send messages to us later
local our_ip, our_port = getLocalIP()
client:sendto("server ip"..our_ip, ip, port)
-- We can now send everyone that has joined this room a packet telling them the amount of players in the room
for i,c in pairs(server_info["clients"]) do
client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
end
else
-- We don't have enough room for another player, so we send the peer a message telling them to disconnect
local reason = "Max players inside the room!"
client:sendto("invalid confirmation"..reason, ip, port)
end
elseif msg == "requesting server info" then
-- We need to find your address, so we create a new UDP socket and assign it a random IP
local our_ip, our_port = getLocalIP()
-- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
local uid = {ip = our_ip}
client:sendto("server info"..json.encode(uid), ip, port)
elseif msg and string.find(msg, "disconnection confirmation") then
-- We know that one of the clients connected to this server wants to leave, so we send a packet back telling them that we have been notified
client:sendto("notified of disconnection", ip, port)
local uid = {ip = ip, port = port}
local clientInfo
local clientState
for i = 1, #server_info["clients"] do
if server_info["clients"][i] == uid then
clientInfo = i
end
end
table.remove(server_info["clients"], clientInfo)
-- We also need to deduct the player count
server_info["players"] = server_info["players"] - 1
elseif msg and string.find(msg, "client state") then
-- A connected peer has sent their client state to us, so we update our world state accordingly
local clientState = json.decode(string.sub(msg, 13, #msg))
local hasClientState = false
local existingState = nil
for i,s in pairs(server_info["world_state"]) do
if s.playerName == clientState.playerName then
hasClientState = true
existingState = s
end
end
if hasClientState == true then
if clientState.sendNumber > existingState.sendNumber then
existingState.playerX = clientState.playerX
existingState.playerY = clientState.playerY
end
else
if not server_info["world_state"][clientState] then
table.insert(server_info["world_state"], clientState)
end
end
end
end
else
if finding_room == false then
local result = client:receive()
if result and string.find(result, "valid confirmation") then
-- We know that we successfully joined the room, and we can now receive messages from the server!
print("Successfully joined the room!")
viewer.preferredFPS = 30
joinedRoom = true
current_time = nil
parameter.clear()
-- We now create a button that allows the player to leave the room
local paramName = {"Leave Room"}
local paramArgs = {function() leaveRoom() end}
resetParameters(paramName, paramArgs)
-- We now set our player name to the ID that the server sent us
local playerID = string.sub(result, 19, #result)
local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2, timestamp = playerTick}
myPlayerClient = playerTab
elseif result and string.find(result, "server ip") then
-- Now that we have the servers IP address, we can message them later if we need to
local serverIp = string.sub(result, 10, #result)
client_info["serverIp"] = serverIp
elseif result and string.find(result, "player count") then
-- We now know the current amount of players in our room, for now at least
local count = string.sub(result, 13, #result)
print("Player count: "..count)
elseif result and string.find(result, "invalid confirmation") then
-- We can now see the reason for our invalid confirmation and display it
local reason = string.sub(result, 21, #result)
print("Could not join the room: "..reason)
current_time = nil
-- We can now also remove the address and port set for our peer
client:setpeername("*")
elseif result == "notified of disconnection" then
-- We know that we can now safely leave the server
client:setpeername("*")
joinedRoom = false
other_ip = nil
print("Successfully left the room!")
local paramNames = {"Create Room", "Join Room", "Find Room"}
local paramArgs = {createRoom, function()
if other_ip then
joinRoom(other_ip, false)
else
parameter.text("IP", "", function(t)
other_ip = t
end)
end
end, findRoom}
resetParameters(paramNames, paramArgs)
elseif result == nil then
if current_time then
if finding_room == false then
if os.time() > current_time + 5 then
-- If we get no response back for 5 seconds or more, we know it didn't work
print("Failed to connect, please try again later")
current_time = nil
end
end
end
end
else
local result, ip, port = client:receivefrom()
if result and string.find(result, "server info") then
-- Now we can attempt to join the room with the given IP address
local tab = json.decode(string.sub(result, 12, #result))
if finding_room == true then
joinRoom(tab.ip, true)
finding_room = false
end
else
if finding_room == true then
if current_time and os.time() > current_time + 15 then
-- If we get no response back for 15 seconds or more, we know we couldn't find a room
print("Failed to find a room!")
current_time = nil
end
end
end
end
end
end
function touched(touch)
if touch.state ~= ENDED and touch.state ~= CANCELLED then
if touch.state == BEGAN or touch.state == CHANGED then
if joinedRoom == true then
if myPlayerClient then
myPlayerClient.playerX = CurrentTouch.x
myPlayerClient.playerY = CurrentTouch.y
sendNumber = sendNumber + 1
local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber}
client:send("client state"..json.encode(playerTab))
end
end
end
else
if joinedRoom == true then
if myPlayerClient then
myPlayerClient.playerX = CurrentTouch.x
myPlayerClient.playerY = CurrentTouch.y
sendNumber = sendNumber + 1
local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber}
client:send("client state"..json.encode(playerTab))
end
end
end
end
function updatePlayers()
-- Here we send a packet to the server every frame, telling them our player position so it can properly update the whole game
-- We now update the position of our player puppets, so it looks like the players are being updated in realtime
for i,p in pairs(server_info["world_state"]) do
ellipse(p.playerX, p.playerY, 35)
end
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
updatePlayers()
receiveData()
end
Both balls show up on iPad 1. Moving just one ball and the response was ok. Moving 2 balls and there is a large delay after awhile. The balls continued to move for about 15 seconds after I stopped moving both fingers.
@dave1707, Try the code now. I edited the code again after doing a little bit of research on multiplayer network latency. I decided to try setting the client’s frame rate to 30 fps when they join the server so even though less packets are being sent, theoretically the server should not get as many packets and so there won’t be as much latency. I even tested a mathematical calculation that allows me to linearly interpolate between the previous position of the player and the new position.
Moving just 1 circle, there’s a delay partway through the move. If I move from bottom to top, halfway up the screen the circle will pause for a fraction of a second. When I move 2 circles for maybe 30 seconds, the circles will continue to move for about 20 seconds after I stop moving my fingers on the screen.