From 6f20a7611ec3fe157a058a9ae233f371f6fd0099 Mon Sep 17 00:00:00 2001 From: Leo Honkanen Date: Sat, 5 Aug 2023 16:06:40 +0300 Subject: [PATCH] support inviting to game over steam friends --- docs/apps.rst | 17 ++++++++++++++++- docs/friends.rst | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/apps.cpp | 14 +++++++++++++- src/friends.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/docs/apps.rst b/docs/apps.rst index 7b047bc..0e9a2b3 100644 --- a/docs/apps.rst +++ b/docs/apps.rst @@ -40,4 +40,19 @@ Function Reference if Steam.apps.isDlcInstalled(12345) then -- Unlock game content - end \ No newline at end of file + end + +.. function:: apps.getLaunchCommandLineParam() + + :returns: (`string`) The launch command line parameters + :SteamWorks: `GetLaunchCommandLine `_ + + Gets the launch command line parameters. Use it to for example parse it for a connect string when implementing game invite functionality using :func:`friends.inviteUserToGame`. + +**Example**:: + + local params = Steam.apps.getLaunchCommandLineParam() + local connect_string = tryParseConnectString(params) + if (connect_string) then + initiateJoinGame(connect_string) + end diff --git a/docs/friends.rst b/docs/friends.rst index 4890950..52fbe38 100644 --- a/docs/friends.rst +++ b/docs/friends.rst @@ -107,6 +107,30 @@ Function Reference } } +.. function:: friends.inviteUserToGame(steam_id, connect_string) + + :param uint64 steam_id: The Steam ID of the other user. + :param string connect_string: Custom string that tells the joining player how to join the game + :returns: True if invite was sent successfully, false if not + :SteamWorks: `InviteUserToGame `_ + + Invites the given user steam_id to the game identified by `connect_string` + + The connect_string can be received by the application on the joining player in two ways depending on whether the game is already running or it is being launched. + + You should implement the callback :func:`friends.onGameRichPresenceJoinRequested` to receive the `connect_string` on the invitee and ultimately establish the connection on an already running application. + + You should also call :func:`apps.getLaunchCommandLineParams` on game launch and check if the game was launched with the `connect_string`, and immediately take steps to establish the connection. + + To add UI elements to invite or join the game over the Steam overlay or friends menu, also set the rich presence key `connect` with the `connect_string` value and clear it when the game is no longer available to join. + +**Example**:: + + + local friend_id = getSteamIdSomehow() + local success = Steam.friends.inviteUserToGame(friend_id, 'serverID=birthday_party') + + Callbacks Reference ------------------- @@ -131,3 +155,23 @@ Callbacks Reference function Steam.friends.onGameOverlayActivated(data) print('Overlay active is', data.active) end + +.. function:: friends.onGameRichPresenceJoinRequested(data) + + :param table data: A table similar to `GameRichPresenceJoinRequested_t `_ + + * **data.steamIDFriend** (`uint64`) -- Steam ID of friend from through the invite was received + * **data.connectString** (`string`) -- custom connect string to parse for connection details + + :returns: nothing + + +**Example**:: + + function Steam.friends.onGameRichPresenceJoinRequested(data) + if (game_state=='main_menu') then + initiateJoinGame(data.connectString) + else + showDialog("you can only join a game from the main menu") + end + end diff --git a/src/apps.cpp b/src/apps.cpp index 43a8a92..2e564fe 100644 --- a/src/apps.cpp +++ b/src/apps.cpp @@ -1,4 +1,6 @@ #include "apps.hpp" +#include + // ============================ // ======== SteamApps ========= @@ -17,12 +19,22 @@ EXTERN int luasteam_isDlcInstalled(lua_State *L) { return 1; } +// int GetLaunchCommandLine( char *pszCommandLine, int cubCommandLine ); +EXTERN int luasteam_getLaunchCommandLine(lua_State *L) { + char *pCommandLine = (char*) malloc(1024); + SteamApps()->GetLaunchCommandLine(pCommandLine, 1024); + lua_pushstring(L, pCommandLine); + free(pCommandLine); + return 1; +} + namespace luasteam { void add_apps(lua_State *L) { - lua_createtable(L, 0, 2); + lua_createtable(L, 0, 3); add_func(L, "getCurrentGameLanguage", luasteam_getCurrentGameLanguage); add_func(L, "isDlcInstalled", luasteam_isDlcInstalled); + add_func(L, "getLaunchCommandLine", luasteam_getLaunchCommandLine); lua_setfield(L, -2, "apps"); } diff --git a/src/friends.cpp b/src/friends.cpp index 1c0ab51..29b9578 100644 --- a/src/friends.cpp +++ b/src/friends.cpp @@ -15,6 +15,7 @@ const char *dialog_types[] = {"friends", "community", "players", "settings", "of class CallbackListener { private: STEAM_CALLBACK(CallbackListener, OnGameOverlayActivated, GameOverlayActivated_t); + STEAM_CALLBACK(CallbackListener, OnGameRichPresenceJoinRequested, GameRichPresenceJoinRequested_t); }; void CallbackListener::OnGameOverlayActivated(GameOverlayActivated_t *data) { @@ -38,6 +39,30 @@ void CallbackListener::OnGameOverlayActivated(GameOverlayActivated_t *data) { } } +void CallbackListener::OnGameRichPresenceJoinRequested(GameRichPresenceJoinRequested_t *data) { + if (data == nullptr) { + return; + } + lua_State *L = luasteam::global_lua_state; + if (!lua_checkstack(L, 4)) { + return; + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, friends_ref); + lua_getfield(L, -1, "onGameRichPresenceJoinRequested"); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + } else { + lua_createtable(L, 0, 2); + luasteam::pushuint64(L, data->m_steamIDFriend.ConvertToUint64()); + lua_setfield(L, -2, "steamIDFriend"); + lua_pushstring(L, data->m_rgchConnect); + lua_setfield(L, -2, "connectString"); + lua_call(L, 1, 0); + lua_pop(L, 1); + } +} + } // namespace // void ActivateGameOverlay( const char *pchDialog ); @@ -71,20 +96,33 @@ EXTERN int luasteam_setRichPresence(lua_State *L) { return 1; } +// bool InviteUserToGame( CSteamID steamIDFriend, const char *pchConnectString ); +EXTERN int luasteam_inviteUserToGame(lua_State *L) { + CSteamID id(luasteam::checkuint64(L, 1)); + const char *connectString = luaL_checkstring(L, 2); + + bool success = SteamFriends()->InviteUserToGame(id, connectString); + lua_pushboolean(L, success); + return 1; +} + namespace luasteam { void add_friends(lua_State *L) { - lua_createtable(L, 0, 4); + lua_createtable(L, 0, 5); add_func(L, "activateGameOverlay", luasteam_activateGameOverlay); add_func(L, "activateGameOverlayToWebPage", luasteam_activateGameOverlayToWebPage); add_func(L, "getFriendPersonaName", luasteam_getFriendPersonaName); add_func(L, "setRichPresence", luasteam_setRichPresence); + add_func(L, "inviteUserToGame", luasteam_inviteUserToGame); lua_pushvalue(L, -1); friends_ref = luaL_ref(L, LUA_REGISTRYINDEX); lua_setfield(L, -2, "friends"); } -void init_friends(lua_State *L) { friends_listener = new CallbackListener(); } +void init_friends(lua_State *L) { + friends_listener = new CallbackListener(); +} void shutdown_friends(lua_State *L) { luaL_unref(L, LUA_REGISTRYINDEX, friends_ref);