gamecreator Posted May 3, 2014 Share Posted May 3, 2014 Hi all! I spent the last few days, after work, learning how Steam code works and incorporating it into a small example project. I learned a bit and thought I'd share with the community, as I would personally have loved to use code like this as a starting point. I hope it helps someone. Explanation of what this all does is after the code. Simply start a new 3.1 C++ project and replace all the code in App.cpp with the below. #include "App.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {} App::~App() { delete world; delete window; } bool App::Start() { window = Leadwerks::Window::Create("SteamTest"); context = Context::Create(window); world = World::Create(); camera = Camera::Create(); window->HideMouse(); // Initialize Steamworks Steamworks::Initialize(); return true; } bool App::Loop() { if(!SteamAPI_IsSteamRunning()) { MessageBoxA(0, "Steam not detected. Please run Steam before running this program.", "Error", 0); return false; } // Exit program if needed if(window->KeyHit(Key::Escape) || window->Closed()) return false; Leadwerks::Time::Update(); world->Update(); world->Render(); context->SetBlendMode(Blend::Alpha); context->SetColor(1, 1, 1); // Go to a website using STEAM browser if(window->KeyHit(Key::W)) SteamFriends()->ActivateGameOverlayToWebPage("http://www.leadwerks.com/"); // Send a message to the first friend on the list (index 0) using STEAM if(window->KeyHit(Key::M)) SteamFriends()->ReplyToFriendMessage(SteamFriends()->GetFriendByIndex(0, k_EFriendFlagImmediate), "Test from program"); context->DrawText("Your user name: ", 10, 30); context->DrawText(SteamFriends()->GetFriendPersonaName(SteamUser()->GetSteamID()), 120, 30); FriendGameInfo_t friendgameinfo; context->DrawText("This game's SteamID: ", 10, 50); SteamFriends()->GetFriendGamePlayed(SteamUser()->GetSteamID(), &friendgameinfo); context->DrawText(String(friendgameinfo.m_gameID.AppID()), 140, 50); int steamfriends = SteamFriends()->GetFriendCount(k_EFriendFlagImmediate); context->DrawText("Friends: " + String(steamfriends), 10, 90); // Cycle through all of your Steam friends for (int i = 0; i < steamfriends; i++) { // Get friend ID CSteamID steamIDFriend = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagImmediate); // Print friend name context->DrawText(SteamFriends()->GetFriendPersonaName(steamIDFriend), 10, 110 + i * 20); // Print friend's Steam status (Offline, Online, Away, etc.) if(SteamFriends()->GetFriendPersonaState(steamIDFriend) == k_EPersonaStateOffline) context->DrawText("Offline", 120, 110 + i * 20); if(SteamFriends()->GetFriendPersonaState(steamIDFriend) == k_EPersonaStateOnline) context->DrawText("Online", 120, 110 + i * 20); if(SteamFriends()->GetFriendPersonaState(steamIDFriend) == k_EPersonaStateBusy) context->DrawText("Busy", 120, 110 + i * 20); if(SteamFriends()->GetFriendPersonaState(steamIDFriend) == k_EPersonaStateAway) context->DrawText("Away", 120, 110 + i * 20); if(SteamFriends()->GetFriendPersonaState(steamIDFriend) == k_EPersonaStateSnooze) context->DrawText("Snooze", 120, 110 + i * 20); // Is friend playing a Steam game right now? if(SteamFriends()->GetFriendGamePlayed(steamIDFriend, &friendgameinfo)) // SteamID of game they're playing, if they're in one context->DrawText(String(friendgameinfo.m_gameID.AppID()),220,110 + i * 20); else context->DrawText("Not in game", 200, 110 + i * 20); // Match a player by name if(strcmp("yourfriendssteamplayername", SteamFriends()->GetFriendPersonaName(SteamFriends()->GetFriendByIndex(i, k_EFriendFlagImmediate))) == 0) { context->DrawText("Player name match", 300, 110 + i * 20); // Do something here like start sending him data... } } context->SetBlendMode(Blend::Solid); context->Sync(false); return true; } This code is an illustration of several things you can do with Steam: If you hit M, it sends a message to the first person on your list (using index 0 in the function). If they're offline then they will receive the message(s) when they get online. I realize the function is called ReplyToFriendMessage but I couldn't find one that was just Send and this was tested and it works, even if it's not a reply (it's the first message sent). If you hit W, it opens a browser inside your game and goes to the website specified. It shows your own Steam name and the Steam AppID of your game (by default it's 480). It shows the total count of your friends on your friends list. Then it cycles through all your friends and displays their name, their Steam status and if they're in a game, the AppID of the game they're in. This is very useful in dealing with only friends that are online and only sending data to people who are already in the same game as you are, if you want to send data directly (not by lobby invite). A lot of this was learned by simply seeing what functions existed but a lot more was by wading through the documentation here: https://partner.steamgames.com/home/steamworks Next up: peer-to-peer networking. Will report back with any success. 14 Quote Link to comment Share on other sites More sharing options...
Einlander Posted May 3, 2014 Share Posted May 3, 2014 I haven't looked at nor do I know how it's done with the steam API, but please do your steam checks exhaustively. I have many games on steam that simply will not run because the steam network went down or the steamworks API is undergoing maintenance. 7 Days to Die is one such game. You can have steam running, be online, but the game will not run because steam was having problems. Quote Link to comment Share on other sites More sharing options...
gamecreator Posted May 3, 2014 Author Share Posted May 3, 2014 Completely agreed. This is an example that's as simple as I could make it to illustrate several Steam functions and be easy to understand. This is definitely not how you would code for public release. It's just a code snippet. Quote Link to comment Share on other sites More sharing options...
Thirsty Panther Posted May 3, 2014 Share Posted May 3, 2014 Nice work. Couple of questions. Does your game have to be greenlite before your code will work? Can the same functions be called in LUA ( I don't have C++ yet ) ? Quote Link to comment Share on other sites More sharing options...
Einlander Posted May 3, 2014 Share Posted May 3, 2014 @ gamecreator understood. Quote Link to comment Share on other sites More sharing options...
gamecreator Posted May 3, 2014 Author Share Posted May 3, 2014 Panther, you can do this right now without being Greenlit. Except for the initialization function it's all done using the Steamworks SDK and I'm not sure if they support Lua (I don't believe so but I'd check their page to make sure). I should also mention that before being Greenlit your game will always be AppID 480 and be called Spacewar. This can't be changed until you're Greenlit (per here). 1 Quote Link to comment Share on other sites More sharing options...
gamecreator Posted May 6, 2014 Author Share Posted May 6, 2014 Here is the data transfer code sample. It very directly just starts sending and receiving coordinates with a Steam friend and moves two names on screen accordingly. As before, create a new project and replace everything in App.cpp with this code. Make sure to change yoursteamname and otherplayersteamname in friendstrings to your Steam name and your friend's Steam name. The program matches your Steam friend based on the exact name you specify. #include "App.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {} App::~App() { delete world; delete window; } bool steamfriendfound = false; // Change yoursteamname and otherplayersteamname to your and other player's actual Steam names // Matching system depends on other player's name being spelled correctly char *friendstrings[2] = { "yoursteamname", "otherplayersteamname" }; int thisplayerindex = 0; CSteamID steamIDFriend; struct posdatastruct { float x, y; } pos[2]; bool App::Start() { window = Leadwerks::Window::Create("SteamTest"); context = Context::Create(window); world = World::Create(); camera = Camera::Create(); window->HideMouse(); Steamworks::Initialize(); // Set initial positions of 2 players pos[0].x=200; pos[0].y=200; pos[1].x=400; pos[1].y=400; return true; } bool App::Loop() { if(!SteamAPI_IsSteamRunning()) { MessageBoxA(0, "Steam not detected. Please run Steam before running this program.", "Error", 0); return false; } // Compare current player's name // If it's the same as the second player (index 1), set it as such // (otherwise leave it index 0) if(strcmp(friendstrings[1],SteamFriends()->GetFriendPersonaName(SteamUser()->GetSteamID()))==0) thisplayerindex=1; // If we haven't found the friend we want to connect to yet... if(!steamfriendfound) { // Cycle through all of your Steam friends for(int i = 0; i < SteamFriends()->GetFriendCount(k_EFriendFlagImmediate); i++) { // Get friend ID steamIDFriend = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagImmediate); // Match connecting player by Steam name if(strcmp(friendstrings[1-thisplayerindex], SteamFriends()->GetFriendPersonaName(steamIDFriend))==0) { steamfriendfound=true; break; } } } // ... otherwise play the game else { // Send data to friend SteamNetworking()->SendP2PPacket(steamIDFriend, &pos[thisplayerindex], sizeof(pos[0]), k_EP2PSendUnreliable); // Read any and all incoming packets uint32 msgSize = 0; while(SteamNetworking()->IsP2PPacketAvailable(&msgSize)) { void *packet = malloc(msgSize); CSteamID steamIDRemote; uint32 bytesRead = 0; // At least make sure message is the right size if(msgSize==sizeof(pos[0])) { if(SteamNetworking()->ReadP2PPacket(&pos[1-thisplayerindex], msgSize, &bytesRead, &steamIDRemote)) { // Any extra code to analyze the packet can go here } } free(packet); } } // Exit program if needed if(window->KeyHit(Key::Escape)||window->Closed()) return false; if(window->KeyDown(Key::Right)) pos[thisplayerindex].x+=2*Time::GetSpeed(); if(window->KeyDown(Key::Left)) pos[thisplayerindex].x-=2*Time::GetSpeed(); if(window->KeyDown(Key::Up)) pos[thisplayerindex].y-=2*Time::GetSpeed(); if(window->KeyDown(Key::Down)) pos[thisplayerindex].y+=2*Time::GetSpeed(); Leadwerks::Time::Update(); world->Update(); world->Render(); context->SetBlendMode(Blend::Alpha); context->SetColor(1, 1, 1); context->DrawText("Use the arrow keys to move your name", 10, 10); context->DrawText(friendstrings[0], (int)pos[0].x, (int)pos[0].y); context->DrawText(friendstrings[1], (int)pos[1].x, (int)pos[1].y); context->SetBlendMode(Blend::Solid); context->Sync(false); // Sleep a bit to not spam connection Sleep(10); return true; } Interesting thing to note: I thought I was being clever by making sure both players are running Steam AppID 480 (our defaul pre-Greenlit game ID). This backfired when my friend was already in another game before running this program. The AppID didn't match because Steam returned the ID of his other game. (It also showed that game on my friend's list, not Spacewar.) As such, I removed that check and not too surprisingly, the program worked anyway. It seems Steam makes sure to send messages between the same games anyway, no matter what else you may have running, so you don't need to bother checking for it. Enjoy! 5 Quote Link to comment Share on other sites More sharing options...
gamecreator Posted May 18, 2014 Author Share Posted May 18, 2014 Here is a Steam lobby example. Sadly, it's not very beginner friendly as it requires callbacks and call results to be used and Steam requires that you do this all within a class. That said, once you wrap your head around it, it's not too bad. As is my preference, I've made this as easy and short as I could. Check out the Getting Started with Steam link to read more abotu callbacks and call results: https://partner.steamgames.com/documentation/getting_started Also, a lot of this was learned from here: https://partner.steamgames.com/documentation/matchmaking This program very simply creates a lobby, invites friends to join and shows you the results on either side (invitor or invitee). #include "App.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {} App::~App() { delete world; delete window; } bool createdlobby = false; bool joinedlobby = false; CSteamID m_steamIDLobby; CSteamID userjoiningorleavinglobby; class LobbyManagerClass { public: LobbyManagerClass(); void createlobby(); // Called when the lobby is created (it's not instant, as it has to run it by Steam first) void OnLobbyCreated(LobbyCreated_t *pCallback, bool bIOFailure); CCallResult<LobbyManagerClass, LobbyCreated_t> m_SteamCallResultLobbyCreated; // Called when invitee enters a lobby void OnLobbyEntered(LobbyEnter_t *pCallback, bool bIOFailure); CCallResult<LobbyManagerClass, LobbyEnter_t> m_SteamCallResultLobbyEntered; // Called every time someone joins or leaves a lobby STEAM_CALLBACK(LobbyManagerClass, OnLobbyChatUpdate, LobbyChatUpdate_t, m_CallbackChatDataUpdate); // Called when someone invites you to a lobby STEAM_CALLBACK(LobbyManagerClass, OnGameLobbyJoinRequested, GameLobbyJoinRequested_t, m_CallbackLobbyDataUpdate); } lobbymanager; LobbyManagerClass::LobbyManagerClass() : m_CallbackLobbyDataUpdate(this, &LobbyManagerClass::OnGameLobbyJoinRequested), m_CallbackChatDataUpdate(this, &LobbyManagerClass::OnLobbyChatUpdate) { } void LobbyManagerClass::createlobby() { SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby(k_ELobbyTypePublic, 16); // Set the call result for when Steam returns lobby creation results m_SteamCallResultLobbyCreated.Set(hSteamAPICall, this, &LobbyManagerClass::OnLobbyCreated); } void LobbyManagerClass::OnLobbyEntered(LobbyEnter_t *pCallback, bool bIOFailure) { // If we didn't get a success confirmation, let joiner know if(pCallback->m_EChatRoomEnterResponse!=k_EChatRoomEnterResponseSuccess) { MessageBoxA(0, "Failed to enter lobby", "Error", 0); return; } // We succesfully entered someone else's lobby m_steamIDLobby = pCallback->m_ulSteamIDLobby; joinedlobby = true; } void LobbyManagerClass::OnLobbyCreated(LobbyCreated_t *pCallback, bool bIOFailure) { // If lobby creation was successful if(pCallback->m_eResult==k_EResultOK) { // Get lobby info m_steamIDLobby = pCallback->m_ulSteamIDLobby; SteamMatchmaking()->SetLobbyData(m_steamIDLobby, "name", "Leadwerkslobby"); createdlobby = true; } else MessageBoxA(0, "Could not create lobby.", "Error", 0); } // This gets called for both the invitor and invitees whenever someone joins or leaves a lobby void LobbyManagerClass::OnLobbyChatUpdate(LobbyChatUpdate_t *pCallback) { // Get the SteamID of the person joining or leaving the lobby userjoiningorleavinglobby = pCallback->m_ulSteamIDUserChanged; } // Called when you accept an invite void LobbyManagerClass::OnGameLobbyJoinRequested(GameLobbyJoinRequested_t *pParam) { // Join lobby SteamAPICall_t hSteamAPICall = SteamMatchmaking()->JoinLobby(pParam->m_steamIDLobby); // Set the call result for when player enters lobby m_SteamCallResultLobbyEntered.Set(hSteamAPICall, this, &LobbyManagerClass::OnLobbyEntered); } bool App::Start() { window = Leadwerks::Window::Create("Steam Lobby Example"); context = Context::Create(window); world = World::Create(); camera = Camera::Create(); window->HideMouse(); Steamworks::Initialize(); return true; } bool App::Loop() { if(!SteamAPI_IsSteamRunning()) { MessageBoxA(0, "Steam not detected. Please run Steam before running this program.", "Error", 0); return false; } // Exit program if needed if(window->KeyHit(Key::Escape) || window->Closed()) return false; // This must be called regularly for callbacks and call results to work SteamAPI_RunCallbacks(); Leadwerks::Time::Update(); world->Update(); world->Render(); context->SetBlendMode(Blend::Alpha); context->SetColor(1, 1, 1); // If no lobby is created or joined, give player option to create one if(!createdlobby && !joinedlobby) { if(window->KeyHit(Key::C)) lobbymanager.createlobby(); context->DrawText("Press C to create a lobby or wait for a friend invite to join one", 10, 30); } // Once a lobby is created, give user option to invite others else if(createdlobby) { context->DrawText("Press I to invite a friend to your lobby.", 10, 10); if(window->KeyHit(Key::I)) SteamFriends()->ActivateGameOverlay("LobbyInvite"); // ... or you can invite friends through the program with the following syntax // InviteUserToLobby(CSteamID steamIDLobby, CSteamID steamIDInvitee); } if(createdlobby) context->DrawText("You created a lobby. Lobby name:", 10, 30); if(joinedlobby) context->DrawText("You joined a lobby. Lobby name:", 10, 30); // Show lobby info if(createdlobby || joinedlobby) { context->DrawText(SteamMatchmaking()->GetLobbyData(m_steamIDLobby, "name"), 220, 30); context->DrawText("Number of lobby members:", 10, 50); context->DrawText(String(SteamMatchmaking()->GetNumLobbyMembers(m_steamIDLobby)), 170, 50); context->DrawText("Members in lobby:", 10, 90); for(int i=0; i<SteamMatchmaking()->GetNumLobbyMembers(m_steamIDLobby); i++) { CSteamID steamIDFriendinlobby = SteamMatchmaking()->GetLobbyMemberByIndex(m_steamIDLobby, i); context->DrawText(SteamFriends()->GetFriendPersonaName(steamIDFriendinlobby), 10, 110+20*i); } } context->SetBlendMode(Blend::Solid); context->Sync(false); Sleep(10); // Sleep a bit to not spam connection return true; } Thanks to Rick for his help in understanding C++ I haven't used before. 1 1 Quote Link to comment Share on other sites More sharing options...
gamecreator Posted May 18, 2014 Author Share Posted May 18, 2014 And here's how to create a lobby with a key that anyone in the world can find and join automatically. I added the requeststeamlobbylist function and the related OnLobbyMatchListCallback callback. #include "App.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {} App::~App() { delete world; delete window; } bool createdlobby = false; bool joinedlobby = false; CSteamID steamIDLobby; CSteamID userjoiningorleavinglobby; class LobbyManagerClass { public: LobbyManagerClass(); void createlobby(); void requeststeamlobbylist(); // Called when the lobby is created (it's not instant, as it has to run it by Steam first) void OnLobbyCreated(LobbyCreated_t *pCallback, bool bIOFailure); CCallResult<LobbyManagerClass, LobbyCreated_t> m_SteamCallResultLobbyCreated; // Called when the lobby is created (it's not instant, as it has to run it by Steam first) void OnLobbyMatchListCallback(LobbyMatchList_t *pCallback, bool bIOFailure); CCallResult<LobbyManagerClass, LobbyMatchList_t> m_SteamCallResultLobbyMatchList; // Called when invitee enters a lobby void OnLobbyEntered(LobbyEnter_t *pCallback, bool bIOFailure); CCallResult<LobbyManagerClass, LobbyEnter_t> m_SteamCallResultLobbyEntered; // Called every time someone joins or leaves a lobby STEAM_CALLBACK(LobbyManagerClass, OnLobbyChatUpdate, LobbyChatUpdate_t, m_CallbackChatDataUpdate); } lobbymanager; LobbyManagerClass::LobbyManagerClass() : m_CallbackChatDataUpdate(this, &LobbyManagerClass::OnLobbyChatUpdate) { } void LobbyManagerClass::createlobby() { SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby(k_ELobbyTypePublic, 16); // Set the call result for when Steam returns lobby creation results m_SteamCallResultLobbyCreated.Set(hSteamAPICall, this, &LobbyManagerClass::OnLobbyCreated); } void LobbyManagerClass::requeststeamlobbylist() { // You can limit the number of results to a specified amount, if you want // SteamMatchmaking()->AddRequestLobbyListResultCountFilter(15); // Steam returns closest results first by geographic IP location so search worldwide SteamMatchmaking()->AddRequestLobbyListDistanceFilter(k_ELobbyDistanceFilterWorldwide); // Let's only find lobbies which have a name of Leadwerks lobby SteamMatchmaking()->AddRequestLobbyListStringFilter("name", "Leadwerkslobby", k_ELobbyComparisonEqual); SteamAPICall_t hSteamAPICall = SteamMatchmaking()->RequestLobbyList(); // Set the call result for when Steam returns a list of lobbies m_SteamCallResultLobbyMatchList.Set(hSteamAPICall, this, &LobbyManagerClass::OnLobbyMatchListCallback); } void LobbyManagerClass::OnLobbyCreated(LobbyCreated_t *pCallback, bool bIOFailure) { // If lobby creation was successful if(pCallback->m_eResult==k_EResultOK) { // Get lobby info steamIDLobby = pCallback->m_ulSteamIDLobby; SteamMatchmaking()->SetLobbyData(steamIDLobby, "name", "Leadwerkslobby"); createdlobby = true; } else MessageBoxA(0, "Could not create lobby.", "Error", 0); } void LobbyManagerClass::OnLobbyMatchListCallback(LobbyMatchList_t *pCallback, bool bIOFailure) { if(!bIOFailure && pCallback->m_nLobbiesMatching>0) { // Join the first lobby on the list steamIDLobby = SteamMatchmaking()->GetLobbyByIndex(0); SteamAPICall_t hSteamAPICall = SteamMatchmaking()->JoinLobby(steamIDLobby); // Set the call result for when player enters lobby m_SteamCallResultLobbyEntered.Set(hSteamAPICall, this, &LobbyManagerClass::OnLobbyEntered); } } void LobbyManagerClass::OnLobbyEntered(LobbyEnter_t *pCallback, bool bIOFailure) { // If we didn't get a success confirmation, let joiner know if(pCallback->m_EChatRoomEnterResponse!=k_EChatRoomEnterResponseSuccess) { MessageBoxA(0, "Failed to enter lobby", "Error", 0); return; } // We succesfully entered someone else's lobby steamIDLobby = pCallback->m_ulSteamIDLobby; joinedlobby = true; } // This gets called for both the invitor and invitees whenever someone joins or leaves a lobby void LobbyManagerClass::OnLobbyChatUpdate(LobbyChatUpdate_t *pCallback) { // Get the SteamID of the person joining or leaving the lobby userjoiningorleavinglobby = pCallback->m_ulSteamIDUserChanged; } bool App::Start() { window = Leadwerks::Window::Create("Steam Lobby Example"); context = Context::Create(window); world = World::Create(); camera = Camera::Create(); window->HideMouse(); Steamworks::Initialize(); return true; } bool App::Loop() { if(!SteamAPI_IsSteamRunning()) { MessageBoxA(0, "Steam not detected. Please run Steam before running this program.", "Error", 0); return false; } // Exit program if needed if(window->KeyHit(Key::Escape) || window->Closed()) return false; // This must be called regularly for callbacks and call results to work SteamAPI_RunCallbacks(); Leadwerks::Time::Update(); world->Update(); world->Render(); context->SetBlendMode(Blend::Alpha); context->SetColor(1, 1, 1); // If no lobby is created or joined, give player option to create one if(!createdlobby && !joinedlobby) { if(window->KeyHit(Key::C)) lobbymanager.createlobby(); if(window->KeyHit(Key::R)) lobbymanager.requeststeamlobbylist(); context->DrawText("Press C to create a lobby or R to refresh lobby list", 10, 30); context->DrawText("No lobby matches yet", 10, 50); } if(createdlobby) context->DrawText("You created a lobby. Lobby name:", 10, 30); if(joinedlobby) context->DrawText("You joined a lobby. Lobby name:", 10, 30); // Show lobby info if(createdlobby || joinedlobby) { context->DrawText(SteamMatchmaking()->GetLobbyData(steamIDLobby, "name"), 220, 30); context->DrawText("Members in lobby:", 10, 50); for(int i=0; i<SteamMatchmaking()->GetNumLobbyMembers(steamIDLobby); i++) { CSteamID steamIDFriendinlobby = SteamMatchmaking()->GetLobbyMemberByIndex(steamIDLobby, i); context->DrawText(SteamFriends()->GetFriendPersonaName(steamIDFriendinlobby), 10, 70+20*i); } } context->SetBlendMode(Blend::Solid); context->Sync(false); Sleep(10); // Sleep a bit to not spam connection return true; } 4 Quote Link to comment Share on other sites More sharing options...
AggrorJorn Posted May 19, 2014 Share Posted May 19, 2014 Good work Gamecreator. This will be a very nice reference for me and a lot of others.. 1 Quote Link to comment Share on other sites More sharing options...
tjheldna Posted June 20, 2014 Share Posted June 20, 2014 How did I miss this thread? Thanks GameCreator 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.