Local Multiplayer Game

@dave1707, at least the server duplication problem is now fixed! I’ll do some more code and see if anything improves :slight_smile:

@dave1707, if this works, when the second player joins that player should show on the first players screen but not have its position updated.

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.text("Server Name", "")
    parameter.action("Host", createServer)
    client_data = {}
    clients = {}
    entities = {}
    servers = {}
    my_client = nil
    my_entity = nil
    my_server = nil
    server_name = ""
    connected = false
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip)
    if my_client == nil then
        local client = socket.udp()
        client:setpeername(ip, "14285")
        client:settimeout(0)
        table.insert(clients, client)
        makePlayer(vec3(0, 0, 10), client)
        positions = {x = entity.position.x, y = entity.position.y, z = entity.position.z}
        my_client = client
        client:send("join")
    end
end

function createServer()
    if my_server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server = server
        server_name = Server_Name
    end
end

function makePlayer(pos, client)
    if not entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.Sphere)
        entity.position = pos
        body = entity:add(craft.rigidbody, DYNAMIC)
        entity:add(craft.shape.sphere, 1)
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        body.linearDamping = 0.3
        my_entity = entity
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    end
end

function draw()
    messenger:sendto("checking for servers", "255.255.255.255", "14285")
    if my_server ~= nil then
        local data, ip, port = my_server:receivefrom()
        if data == "checking for servers" then
            my_server:sendto("server available"..my_server:getsockname(), ip, tostring(port))
        elseif data ~= nil then
            if data == "join" then
                print("A client has successfully joined!")
                local uid = {ip = ip, port = port}
                if not client_data[uid] then
                    table.insert(client_data, uid)
                end
                for i,cl in pairs(client_data) do
                    my_server:sendto("make a player", cl.ip, cl.port)
                end
            end
        end
    end
    local data = messenger:receive()
    if data ~= nil then
        if string.find(data, "server available") then
            local address = string.sub(data, 17, #data)
            parameter.action(address, function() joinServer(address) end)
        end
    end
    for i,c in pairs(clients) do
        local data = c:receive()
        if data ~= nil then
            if data == "make player" then
                makePlayer(vec3(0, 0, 10), c)
            end
        end
    end
end

@Creator27 Only 1 player was on each iPad.

@dave1707, I think I figured out why it isn’t working. I need to broadcast the message to every connected iPad and in order to do that I need to send my message via the broadcast address or 255.255.255.255. I’ve edited my code so that it does exactly that.

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.text("Server Name", "")
    parameter.action("Host", createServer)
    client_data = {}
    clients = {}
    entities = {}
    servers = {}
    my_client = nil
    my_entity = nil
    my_server = nil
    server_name = ""
    connected = false
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip)
    if my_client == nil then
        local client = socket.udp()
        client:setpeername(ip, "14285")
        client:settimeout(0)
        table.insert(clients, client)
        makePlayer(vec3(0, 0, 10), client)
        positions = {x = entity.position.x, y = entity.position.y, z = entity.position.z}
        my_client = client
        client:send("join")
    end
end

function createServer()
    if my_server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server = server
        server_name = Server_Name
    end
end

function makePlayer(pos, client)
    if not entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.Sphere)
        entity.position = pos
        body = entity:add(craft.rigidbody, DYNAMIC)
        entity:add(craft.shape.sphere, 1)
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        body.linearDamping = 0.3
        my_entity = entity
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    end
end

function draw()
    messenger:sendto("checking for servers", "255.255.255.255", "14285")
    if my_server ~= nil then
        local data, ip, port = my_server:receivefrom()
        if data == "checking for servers" then
            my_server:sendto("server available"..my_server:getsockname(), ip, tostring(port))
        elseif data ~= nil then
            if data == "join" then
                print("A client has successfully joined!")
                local uid = {ip = ip, port = port}
                if not client_data[uid] then
                    table.insert(client_data, uid)
                end
                my_server:sendto("new", ip, port)
            end
        end
    end
    local data = messenger:receive()
    if data ~= nil then
        if string.find(data, "server available") then
            local address = string.sub(data, 17, #data)
            parameter.action(address, function() joinServer(address) end)
        end
    end
    for i,c in pairs(clients) do
        local data = c:receive()
        if data ~= nil then
            if data == "make player" then
                makePlayer(vec3(0, 0, 10), c)
                for i,e in pairs(entities) do
                    local tab = {x = e.position.x, y = e.position.y, z = e.position.z}
                    my_server:sendto(json.encode(tab).."entity position"..tostring(e), "255.255.255.255", "14285")
                end
            elseif data == "new" then
                my_server:sendto("make player", "255.255.255.255", "14285")
            elseif string.find(data, "entity position") then
                local tab = json.decode(data)
                for i,e in pairs(entities) do
                    if string.find(data, tostring(e)) then
                        e.position = vec3(tab.x, tab.y, tab.z)
                    end
                end
            end
        end
    end
end

@Creator27 Nothing changed. Only one sphere on each iPad. Maybe you need to wait until you can use a second iPad, that would make it a lot easier.

The image is the same as the one above.

@dave1707, I’ll do some more research and see if I can find anything about syncing players across devices.

@dave1707, I really don’t know what else to do, but I’ve edited the code again.

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.text("Server Name", "")
    parameter.action("Host", createServer)
    client_data = {}
    clients = {}
    entities = {}
    servers = {}
    my_client = nil
    my_entity = nil
    my_server = nil
    server_name = ""
    connected = false
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip)
    if my_client == nil then
        local client = socket.udp()
        client:setpeername(ip, 14285)
        client:setoption("broadcast", true)
        client:settimeout(0)
        table.insert(clients, client)
        my_client = client
        client:send("join", "255.255.255.255", 14285)
    end
end

function createServer()
    if my_server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server = server
        server_name = Server_Name
    end
end

function makePlayer(pos, client)
    if not entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.Sphere)
        entity.position = pos
        body = entity:add(craft.rigidbody, DYNAMIC)
        entity:add(craft.shape.sphere, 1)
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        body.linearDamping = 0.3
        my_entity = entity
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    end
end

function draw()
    messenger:sendto("checking for servers", "255.255.255.255", "14285")
    if my_server ~= nil then
        local data, ip, port = my_server:receivefrom()
        if data == "checking for servers" then
            my_server:sendto("server available"..server:getsockname(), ip, tostring(port))
        elseif data ~= nil then
            if data == "join" then
                print("A client has successfully joined!")
                local uid = {ip = ip, port = port}
                if not client_data[uid] then
                    table.insert(client_data, uid)
                end
                makePlayer(vec3(0, 0, 10))
            end
        end
    end
    local data, ip, port = messenger:receivefrom()
    if data ~= nil then
        if string.find(data, "server available") then
            local address = string.sub(data, 17, #data)
            parameter.action(address, function() joinServer(address) end)
            local tab = {ip = ip, port = port}
            for i,c in pairs(clients) do
                table.insert(tab, tostring(c))
            end
        end
    end
end

@Creator27 Same thing, only 1 sphere per iPad.

@dave1707, I just can’t seem to figure out how to broadcast the “make player” message using 255.255.255.255 because connected UDP objects (clients) can only receive messages from the server, and I’ve tried broadcasting it from the server but that didn’t work either.

@dave1707, I’ve rewritten the code so that it broadcasts a message across the network using 255.255.255.255 to every server that says “make a player” with the specified client that joined attached to the end of the message. If the server receives the message, it will go through every connected client and see if it has its name in it. If it does, it will make a player.


function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.text("Server Name", "")
    parameter.action("Host", createServer)
    client_data = {}
    clients = {}
    entities = {}
    servers = {}
    my_client = nil
    my_entity = nil
    my_server = nil
    server_name = ""
    connected = false
    allowed_to_host = true
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip)
    if my_client == nil then
        local client = socket.udp()
        client:setpeername(ip, 14285)
        client:settimeout(0)
        table.insert(clients, client)
        my_client = client
        client:send("join")
        local ip, port, type = my_server:getsockname()
        local bytes, err = messenger:sendto("make a player"..tostring(client)..tostring(my_server), "255.255.255.255", port)
    end
end

function createServer()
    if my_server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server = server
        server_name = Server_Name
    end
end

function stopServer()
    if my_server ~= nil then
        server:close()
    end
end

function makePlayer(pos, client)
    if not entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.Sphere)
        entity.position = pos
        body = entity:add(craft.rigidbody, DYNAMIC)
        entity:add(craft.shape.sphere, 1)
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        body.linearDamping = 0.3
        my_entity = entity
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    end
end

function draw()
    if my_client == nil then
        messenger:sendto("checking for servers", "255.255.255.255", "14285")
    end
    if my_server ~= nil then
        local data, ip, port = my_server:receivefrom()
        if data == "checking for servers" then
            my_server:sendto("server available"..server:getsockname(), ip, tostring(port))
        elseif data ~= nil then
            if data == "join" then
                print("A client has successfully joined!")
                local uid = {ip = ip, port = port}
                if not client_data[uid] then
                    table.insert(client_data, uid)
                end
            elseif string.find(data, "make a player") then
                for i,c in pairs(clients) do
                    if string.find(data, tostring(c)) then
                        makePlayer(vec3(0, 0, 10), c)
                    else
                        if string.find(data, tostring(my_server)) then
                            makePlayer(vec3(0, 0, 10))
                        end
                    end
                end
            end
        end
    end
    local data, ip, port = messenger:receivefrom()
    if data ~= nil then
        if string.find(data, "server available") then
            if allowed_to_host == true then
                local address = string.sub(data, 17, #data)
                parameter.action(Server_Name.." - Join", function() joinServer(address) end)
                local tab = {ip = ip, port = port}
                for i,c in pairs(clients) do
                    table.insert(tab, tostring(c))
                end
                allowed_to_host = false
            end
        end
    end
    for i,c in pairs(clients) do
        local data = c:receive()
        if data == "make a player" then
            makePlayer(vec3(0, 0, 10), c)
        end
    end
end

@Creator27 Same thing, Only 1 sphere per iPad.

Maybe you should re-edit all of the above posts and remove the code. This is getting long with a lot of non working code.

@dave1707, good idea. I will do just that.

@dave1707, can you check to see if both iPads print “make a player”. I need to know to broadcasting across devices is working.

@Creator27 Here something I found among my projects. I think this is supposed to send to everyone.

function setup()   
    myAddress()
end 

function myAddress()
    local socket = require("socket")
    local s = socket.udp()
    s:setsockname("*",0)
    s:setoption("broadcast", true)
    local ip,port,typ = s:getsockname()
    print("ip "..ip,port,typ)
    local bytes,err = s:sendto("hello","255.255.255.255",port)
    if not err then
        s:settimeout(3)
        local msg,myIP,myPort = s:receivefrom()
        print("myIp "..myIP)
        print("myPort "..myPort)
        print("msg "..msg)
    else
        print("PROBLEM: "..err)
    end
end

@Creator27 I didn’t see “make a player” on either iPad. Just successfully joined.

@dave1707, that’s what I tried to do. It’s just not working and it’s getting really annoying!

@Creator27 Normally I would try to help figure out what’s wrong, but at the moment I’m not too interested in coding. For the past few days I’ve been battling the side effects of BA-5 Covid. I feel like I have the worst cold I’ve ever had and almost non stop coughing. So I’ll keep testing your code, but that’s about as far as my interest goes at the moment.

@dave1707, I’m so sorry that’s happening. In fact if I’m being perfectly honest with you, I’m so grateful that you’re even testing this for me so thank you. Um, I just changed the code above slightly and hopefully that works! :slight_smile:

@Creator27 Same thing, one sphere per iPad with the joined successfully message.

@dave1707, I’m currently experimenting with my code seeing how I can get it to sync new and existing players, and I decided to try and make 2 clients join at once. You’ll only need to use one iPad for this, but you can try and use 2 iPads if you want.

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.text("Server Name", "")
    parameter.action("Host", createServer)
    client_data = {}
    clients = {}
    client_count = 0
    entities = {}
    servers = {}
    my_client = nil
    my_entity = nil
    my_server = nil
    server_name = ""
    connected = false
    allowed_to_host = true
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip)
    if my_client == nil then
        local client = socket.udp()
        client:setpeername(ip, 14285)
        client:settimeout(0)
        table.insert(clients, client)
        my_client = client
        client:send("join")
        local client2 = socket.udp()
        client2:setpeername(ip, 14285)
        client2:settimeout(0)
        table.insert(clients, client2)
        client2:send("join")
    end
end

function createServer()
    if my_server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server = server
        server_name = Server_Name
    end
end

function stopServer()
    if my_server ~= nil then
        server:close()
    end
end

function makePlayer(pos, client)
    if not entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.Sphere)
        entity.position = pos
        body = entity:add(craft.rigidbody, DYNAMIC)
        entity:add(craft.shape.sphere, 1)
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        body.linearDamping = 0.3
        my_entity = entity
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    end
end

function draw()
    messenger:sendto("checking for servers", "255.255.255.255", "14285")
    if my_server ~= nil then
        local data, ip, port = my_server:receivefrom()
        if data == "checking for servers" then
            my_server:sendto("server available", ip, port)
        elseif data == "join" then
            local uid = {ip = ip, port = port}
            if not client_data[uid] then
                table.insert(client_data, uid)
            end
            my_server:sendto("update players", ip, port)
        end
    end
    local data, ip, port = messenger:receivefrom()
    if data == "server available" then
        parameter.action(tostring(port).." - Join", function() joinServer(ip) end)
    end
    for i,c in pairs(clients) do
        local data = c:receive()
        if data ~= nil then
           if string.find(data, "update players") then
                makePlayer(vec3(0, 0, 10))
            end
        end
    end
end