heroiclabs / nakama-defold

Defold client for Nakama server.
https://heroiclabs.com
Apache License 2.0
74 stars 12 forks source link

Missing docs #45

Closed SamsTheGreatest closed 2 years ago

SamsTheGreatest commented 2 years ago

Thanks for the client implementation! I am trying to make this example work https://defold.com/2021/03/02/Creating-online-games-using-Nakama-and-Defold/ but I was not able to use the same matchmaking function. It seems to have been renamed to matchmaker_add. Now I'm trying to make that work. https://heroiclabs.com/docs/nakama/concepts/multiplayer/matchmaker/ would probably have been helpful but docs are missing there. Would it be possible to restore them?

britzl commented 2 years ago

https://heroiclabs.com/docs/nakama/concepts/multiplayer/matchmaker/ would probably have been helpful but docs are missing there. Would it be possible to restore them?

@tomglenn is working on updated Defold documentation. I do not know the status of this.

I am trying to make this example work https://defold.com/2021/03/02/Creating-online-games-using-Nakama-and-Defold/ but I was not able to use the same matchmaking function. It seems to have been renamed to matchmaker_add. Now I'm trying to make that work.

Yeah, that article (and accompanying example project) will have to be updated at some point to match the new Nakama release.

What you want to do now is to call the matchmaker_add() function on the client socket:

local client = nakama.create_client(config)
local socket = client.create_socket()
local result = socket.matchmaker_add(2, 2, "*")
SamsTheGreatest commented 2 years ago

Thanks @britzl. @tomglenn do you know how long is it going to take? Then I would syspect there is something wrong implementaiton of the function perhaps? Passing just 2 of those values will result in assertion error as nil values for other parameters are not allowed?

There is a parameter count_multiple which is not mentioned in any docs. And even if I run the function like solocal result = socket.matchmaker_add(2, 2,"*",{},{},10) I get EVENT_DISCONNECTED: Server closing (1005). Reason: ''. Could there be something wrong on the server side I did not set up properly?

Everything works if I replace those lines in socket.lua:

assert((not string_properties) or _G.type(string_properties) == 'table')
assert((not numeric_properties) or _G.type(numeric_properties) == 'table')
assert((not count_multiple) or _G.type(count_multiple) == 'number')

It's basically allowing to not provide other parameters.

SamsTheGreatest commented 2 years ago

Also was meant to mention, log statements across lua files don't do anything, were we supposed to call log.print() in each file to make debugging work?

tomglenn commented 2 years ago

Hi @SamsTheGreatest, we do not currently have an ETA on when the full Defold Client SDK docs will be updated to the latest 3.0 release, however it is something we are actively working on. I apologise I do not have a more concrete answer than this at this time.

britzl commented 2 years ago

Passing just 2 of those values will result in assertion error as nil values for other parameters are not allowed?

Ah, hmm, yes, maybe we should accept nil for any value. There is however nothing in the API spec which says if an argument is required or optional.

@tomglenn do the other engine APIs accept null values for function arguments?

Also was meant to mention, log statements across lua files don't do anything, were we supposed to call log.print() in each file to make debugging work?

The log is silent by default. You can call log.print() once to switch to using print() logging.

britzl commented 2 years ago

There is a parameter count_multiple which is not mentioned in any docs

https://github.com/heroiclabs/nakama-common/blob/master/rtapi/realtime.proto#L378

tomglenn commented 2 years ago

@britzl For this function specifically yes, the .NET client does allow null values for example (https://github.com/heroiclabs/nakama-dotnet/blob/master/Nakama/Socket.cs#L202).

SamsTheGreatest commented 2 years ago

Hi @SamsTheGreatest, we do not currently have an ETA on when the full Defold Client SDK docs will be updated to the latest 3.0 release, however it is something we are actively working on. I apologise I do not have a more concrete answer than this at this time.

Thanks for clarifications. Would it be possible for me to refer to something apart from source code when waiting for docs from your side? I am trying to assemble a match-based game and have some questions regarding the development. Would be very cool to have updated docs and more examples ;)

Btw, when trying to test matchmaker API, what is the best practice? Do I have to clone the project manually and then run another client, simulating a new user trying to join the match? Or is there something more neat to test if I can create a match? I have some more devices at hand, could this be helpful? Maybe you could give some tips :)

SamsTheGreatest commented 2 years ago

@SamsTheGreatest For this function specifically yes, the .NET client does allow null values for example (https://github.com/heroiclabs/nakama-dotnet/blob/master/Nakama/Socket.cs#L202).

I am not sure if I can propose changes to your code but check the snippet above, worked for me:) I have added a pull request with the proposed change.

britzl commented 2 years ago

Btw, when trying to test matchmaker API, what is the best practice? Do I have to clone the project manually and then run another client, simulating a new user trying to join the match? Or is there something more neat to test if I can create a match? I have some more devices at hand, could this be helpful? Maybe you could give some tips :)

The problem is that you can't launch multiple instances of your project from the editor. What I usually do is to drop a Defold engine binary in the root of my project and use that to launch additional instances:

  1. Launch game client number one from the Defold editor (Project->Build)
  2. Launch game client number two+ from a terminal/command prompt by running a copy of dmengine from the project root (download from http://d.defold.com/stable/)
SamsTheGreatest commented 2 years ago

@britzl so if I do open -n -a defold and open the same project there its not the same? Could you clarify how to run a copy of dmengine the way you describe? I have one defold app in my Applications folder. dmengine shall be placed in the folder of the project itself? is it different from defold.app?

britzl commented 2 years ago

dmengine shall be placed in the folder of the project itself? is it different from defold.app?

dmengine is the game engine itself. Defold.app is the editor, build tools etc

SamsTheGreatest commented 2 years ago

@britzl what would be the right dmengine file I need to download from there? And how do I use it afterwards?

britzl commented 2 years ago

what would be the right dmengine file I need to download from there

From your other comments it looks like you are using a Mac. You then need an x86_64-darwin executable. Here's the one for Defold 1.3.2:

http://d.defold.com/archive/stable/287c945fab310c324493e08b191ee1b1538ef973/engine/x86_64-darwin/dmengine

You put it in the root of your project and run it from the terminal:

$> ./dmengine

You probably also need to set the executable flag on the file:

$> chmod +x dmengine
SamsTheGreatest commented 2 years ago

@britzl works like a charm! But then I need to make changes to the Nakama plugin (due to corrupted code). I can do it within the editor but changes will not be saved. I downloaded it manually and store it in the same fashion as the defold does by "default" (hehe). That seems to have worked, but now websocket extension is complaining (only! when running via ./dmengine), specifically DEBUG:SCRIPT: nakama/engine/defold.lua:152: attempt to index global 'websocket' (a nil value) - guess it refers to that it cant find connect method in the table?. I did not download it in the same fashion, just provided as a dependency in the project file. I still don't understand the mechanics of how websocket is being loaded in the code without require("websocket") statement tbh...

britzl commented 2 years ago

Ah, sorry about that! You are using extensions. Doh. My mistake. Then you need a version of the engine with the websocket extension included. This custom version with your extensions can be found in PROJECT_ROOT/build/x86_64-osx/dmengine

SamsTheGreatest commented 2 years ago

Thanks @britzl I am able to run it now! :)

Just trying to make the basics work..

So now I am able to create amultiple instances of clients. I do authetication by email, and i generate a random email and username for each client. Server dashboard shows 4 active sessions when I have 4 clients (happy times I guess?).. Aight so when I then do

local result = socket.matchmaker_add(2, 2,"*")
pprint(result)

output - > DEBUG:SCRIPT:  { --[[0x11af6fae0]]
  cid = "1",
  matchmaker_ticket = { --[[0x11af6fbb0]]
    ticket = "8314f4eb-902c-4b0f-bc1c-8b3465a58496"
  }
}

Happy times again?

additionally, before calling matchmaker_add I registered events:

socket = client.create_socket()

socket.on_matchmaker_matched(function(message)
  log('We are matching')
  local matched = message.matchmaker_matched -- make sure we got matched
  if matched then
    print(matched.match_id)
    print(matched.token)
  end
end)

socket.on_match_presence_event(function(message)
  log("on_match_presence_event:",message)
end)

socket.on_match_data(function(message)
  log("on_match_data:",message)
end)

local ok, err = socket.connect()

And on the server I have main.lua with:

function matchmaker_matched(context, matched_users)
  log('hello world')
end
nk.register_matchmaker_matched(matchmaker_matched)

When I start the server it says Registered Lua runtime Matchmaker Matched function invocation, but the function matchmaker_matched never seems to be called, when clients are connected. Neither there is any print statement on the client..

Am I missing something?

britzl commented 2 years ago

And on the server I have main.lua with:

function matchmaker_matched(context, matched_users)
  log('hello world')
end
nk.register_matchmaker_matched(matchmaker_matched)

When I start the server it says Registered Lua runtime Matchmaker Matched function invocation, but the function matchmaker_matched never seems to be called, when clients are connected. Neither there is any print statement on the client..

I can't remember the details of the server I'm afraid. The snippet of code you shared for main.lua, is that actually all you have in the file or did you extract only that bit? Make sure you have a structure similar to this:

https://github.com/defold/game-xoxo-nakama-server/blob/main/main.lua

SamsTheGreatest commented 2 years ago

@britzl I do imports, of course, tried also with run_once just like in your example but nothing gets printed neither on the server nor on the client. I even copied all code from your server example and still get the same result.

Tried giving extra arguments to matchmaker_add but all the same doesn't work for some reason...

Once client connects the server only outputs this:

nakama_1       | {"level":"info","ts":"2022-05-11T16:43:10.607Z","caller":"server/session_ws.go:80","msg":"New WebSocket session connected","uid":"e8541466-c7da-410d-bdd7-eb35b7ef739e","sid":"71584884-d149-11ec-888d-7106fdcb5b46","format":0}
nakama_1       | {"level":"debug","ts":"2022-05-11T16:43:10.649Z","caller":"server/pipeline.go:65","msg":"Received *rtapi.Envelope_MatchmakerAdd message","uid":"e8541466-c7da-410d-bdd7-eb35b7ef739e","sid":"71584884-d149-11ec-888d-7106fdcb5b46","cid":"1","message":{"MatchmakerAdd":{"min_count":2,"max_count":2}}}
nakama_1       | {"level":"debug","ts":"2022-05-11T16:43:10.651Z","caller":"server/session_ws.go:395","msg":"Sending *rtapi.Envelope_MatchmakerTicket message","uid":"e8541466-c7da-410d-bdd7-eb35b7ef739e","sid":"71584884-d149-11ec-888d-7106fdcb5b46","envelope":"cid:\"1\" matchmaker_ticket:{ticket:\"d8cd02f2-8982-4536-80b2-eb8ec4a61bf6\"}"}

Thanks for your help!

SamsTheGreatest commented 2 years ago

It worked after upgrading server to 3.11.0, praise the lord

britzl commented 2 years ago

Happy to hear you got it working!

SamsTheGreatest commented 2 years ago

@britzl could I ask another thing. When authenticated does it make sense to store a session as a variable? For example I have a file client_nakama.lua that I then import into main.script

In client_nakama.lua:

local client
local session
local socket
local account

M = {}
function M.create_client()
  local config = {}
  config.host = sys.get_config("nakama.host", "127.0.0.1")
  config.port = tonumber(sys.get_config("nakama.port", "7350"))
  config.use_ssl = (config.port == 443)
  config.username = sys.get_config("nakama.server_key", "defaultkey")
  config.password = ""
  config.engine = defold
  config.timeout = 10
  client = nakama.create_client(config)
end

function M.authenticate_email()
  local email = "super@heroes.com"
  local password = "batsignal"
  nakama.sync(function() 
    session = client.authenticate_email(email, password) --client table is accessible from here
    pprint(session) -- prints session
    nakama.set_bearer_token(client, session.token)
  end)

  pprint(session) -- no print!!!!
end

return M

in main.script in init (I guess we only must do it in init?) I import client.lua and then do

local nakama_client = require 'main/client_nakama'
nakama_client.create_client()
nakama_client.authenticate_email()

The first print statement of the session is executed. The second one gives session->nil as we set on top of the lua script. So I have to do everything in a single sync call?

Imagine I have an app, launch it, do login via email, and get my session (which is only accessible in coroutine). Can I use this session somehow during other stages of the game? Like when a player tries to join a match, or do I have to get a new session (reauthenticate)? Do I save the session token and then restart the session when a player wants to join a match? What's the right workflow? Guess all via callbacks as in your xoxo example?

britzl commented 2 years ago

Can I use this session somehow during other stages of the game? Like when a player tries to join a match, or do I have to get a new session (reauthenticate)? Do I save the session token and then restart the session when a player wants to join a match? What's the right workflow? Guess all via callbacks as in your xoxo example?

The recommended way is to:

To allow for relogin for a returning user you can also store the token and check if it has expired and only re-authenticate if the token has expired.

All of this is mentioned in the Sessions section of the readme:

https://github.com/heroiclabs/nakama-defold#sessions

Also a note on your example:

function M.authenticate_email()
  local email = "super@heroes.com"
  local password = "batsignal"
  nakama.sync(function() 
    session = client.authenticate_email(email, password) --client table is accessible from here
    pprint(session) -- prints session
    nakama.set_bearer_token(client, session.token)
  end)

  pprint(session) -- no print!!!!
end

The reason why the pprint() at the end of the authenticate_email() function is nil is because the code inside the nakama.sync() block is asynchronous and involves server communication etc. The pprint() below the nakama.sync() will run immediately and before the code inside the sync has finished.

SamsTheGreatest commented 2 years ago

@britzl Looking at your example here https://github.com/defold/game-xoxo-nakama-client/blob/main/main/xoxo_nakama.lua I was trying to implement something similar. I have a game.script where I do all drawing, spawning objects and such, as well as recording player inputs. I have added middle-layer client.lua which handles all nakama-related things. I then import this module into all other defold scipts such as game.script.

Now, in game.script I call client.lua module, I am able to log in, and add event hooks (guess that's what you call them). I reached the stage when the server adds us to a match and sends back game states, I am able to receive them no problem.

Now I am trying to send inputs from game.script to client.lua somehow. You did it in reverse, you import game.script into client.lua and then hook an input event using game.on_player_input(callback).

Do you think I would be able to communicate with game.script with my setup in any way? Specifically, I am trying to send match state to game.script and send user inputs to client.lua.

client.lua to game.script:

nakama.sync(function()

  local socket = nakama_socket.create(client)
  ...
  socket.on_matchmaker_matched(function(message)
        local match_id = message.matchmaker_matched.match_id   <-- match_id is only available within sync call right?
        ...
  end)

  socket.on_match_data(function(message)
        local data = json.decode(message.match_data.data)
        local op_code = tonumber(message.match_data.op_code)
        if op_code == 1 then
          msg.post('game#game', 'update_state', data)     <-- will this send the match state to game.script?
  end)

end)

game.script to client.lua:

function on_input(self, action_id, action)
    if action_id == hash("jump") then
              msg.post('client', 'jump')     <-- will this send the input to client.lua?
        end

I need match_id to be able to send inputs to nakama, but its locked inside the nakama.sync, so I need to define some function in the nakama.syncscope just like you did in your code.

Is it possible to implement this with my setup? I don't think doing it in reverse like in your script is good for my case as I import client.lua in a lot of script files over game lifecycle.

All those nested callbacks look like spaghetti to me for now 🤪. Wonder if its possible to bring some structure to it.

britzl commented 2 years ago

msg.post('client', 'jump') <-- will this send the input to client.lua?

You can't post messages to Lua modules. I think I'd create a client.lua module and let that handle communication with Nakama and let it receive and send user input/actions:


-- client.lua
local M = {}

-- these are values that you need access to from all functions in this module
local current_match_id = nil
local client = nil
local socket = nil

-- send a message to game.script on the game object name "game"
local function send_to_game(message_id, message)
    msg.post("game#game", message_id, message or {})
end

local function on_matchmaker_matched(message)
    current_match_id = message.matchmaker_matched.match_id
    send_to_game("matchmaker_matched")
end

local function on_match_data(message)
    local data = json.decode(message.match_data.data)
    local op_code = tonumber(message.match_data.op_code)
    if op_code == 1 then
        send_to_game.post('update_state', data)
    end
end

-- login
-- create client and socket
-- set up event handlers
function M.login(email, password)

    nakama.sync(function()

        -- create and authenticate client
        client = nakama.create_client(config)
        local session = client.authenticate_email(email, password)
        -- handle client connect errors
        ...
        nakama.set_bearer_token(client, session.token)

        -- create and connect socket
        socket = nakama_socket.create(client)
        local ok, err = socket.connect()

        -- handle socket connect errors
        ...

        -- set up socket event handlers
        socket.on_matchmaker_matched(on_matchmaker_matched)
        socket.on_match_data(on_match_data)
    end)
end

function M.jump()
    -- buffer and or send here
end

return M
-- game.script

local client = require "client"

function on_input(self, action_id, action)
    if action_id == hash("jump") then
              client.jump()
        end
end
SamsTheGreatest commented 2 years ago

@britzl thank you, I made it work your way! However when sending an encoded message to the server though, I get:

on server:

nakama_1       | {"level":"warn","ts":"2022-05-18T16:25:43.463Z","caller":"server/session_ws.go:239","msg":"Received malformed payload","uid":"8fe7bfbe-613e-4e28-8d8b-13e716e63776","sid":"21a1a0a4-d6c7-11ec-89a6-7106fdcb5b46","data":"eyJtYXRjaF9kYXRhX3NlbmQiOnsib3BfY29kZSI6MiwiZGF0YSI6IntcImxlZnRcIjpmYWxzZSxcImp1bXBcIjp0cnVlLFwicmlnaHRcIjpmYWxzZX0iLCJtYXRjaF9pZCI6ImFlZTMzMDdhLTUwZmQtNGJkOS1iZTI3LTM0M2ZlZDA0YmEwYi5uYWthbWExIn0sImNpZCI6IjMifQ=="}

decoding data gives:

{"match_data_send":{"op_code":2,"data":"{\"left\":false,\"jump\":true,\"right\":false}","match_id":"aee3307a-50fd-4bd9-be27-343fed04ba0b.nakama1"},"cid":"3"}

original message before encoding:

action = {jump = true,left = false,right = false}

The only error message in Defold logs is ERROR:WEBSOCKET: Failed to setup callback

Does it make sense or am I doing something wrong again? 😅

Something similar here but it seems like it's not the OP_CODE that's causing the trouble:

https://github.com/heroiclabs/nakama-godot/issues/16

https://github.com/heroiclabs/nakama-godot/issues/101

SamsTheGreatest commented 2 years ago

There seems to be a typo either in socket_send or match_data_send of socket.lua, when composing a message there is match_data_send but socket_send wants just match_data. As a result, b64 encoding is ommited. I am not sure which one was intended to be used here.

SamsTheGreatest commented 2 years ago

Tested, all stuff in socket_send shall be renamed to match_data_send.

britzl commented 2 years ago

Good catch!