### Quick Start: Creating SyncVars Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Demonstrates the basic usage of creating host-authoritative and client-owned synchronized variables. ```APIDOC ## Quick Start ```csharp // Host-authoritative: only host can modify, all can read var roundNumber = client.CreateHostSyncVar("Round", 1); // Client-owned: each client owns their value, all can read everyone's var isReady = client.CreateClientSyncVar("Ready", false); ``` ``` -------------------------------- ### Quick Start: Create SyncVars Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Instantiate host-authoritative or client-owned synchronized variables with minimal code. ```csharp // Host-authoritative: only host can modify, all can read var roundNumber = client.CreateHostSyncVar("Round", 1); // Client-owned: each client owns their value, all can read everyone's var isReady = client.CreateClientSyncVar("Ready", false); ``` -------------------------------- ### Test Execution Flow Example Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Illustrates the console output during an integration test, showing client initialization, lobby creation, and value synchronization. ```text === Test: Host sets value, client receives update === [TestHost] Initialized successfully (Steam ID: 76561197960265728) [TestClient1] Initialized successfully (Steam ID: 76561197960265729) [TestHost] Created lobby: 109775241870811136 [TestClient1] Joined lobby: 109775241870811136 Host setting value to 42... Client received value change: 0 -> 42 ✓ Test passed: Client received host's value update ``` -------------------------------- ### Quick Start: Lobby Operations Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/lobby-management.md Initialize the client, create a friends-only lobby, join an existing lobby, leave the current lobby, retrieve members, and invite friends. ```csharp SteamNetworkClient client = new SteamNetworkClient(); client.Initialize(); // Create a friends-only lobby with up to 8 members LobbyInfo lobby = await client.CreateLobbyAsync(ELobbyType.k_ELobbyTypeFriendsOnly, 8); // Join an existing lobby await client.JoinLobbyAsync(lobbyId); // Leave the current lobby client.LeaveLobby(); // Read current members List members = client.GetLobbyMembers(); // Invite a friend or open Steam overlay invite client.InviteFriend(friendId); client.OpenInviteDialog(); ``` -------------------------------- ### Initialize and Manage Audio Streams Source: https://github.com/ifbars/steamnetworklib/blob/master/Examples/AudioStreamingExample/README.md Basic usage for initializing the audio manager, starting and stopping streams, and updating the manager. Ensure `Update()` is called regularly. ```csharp // Initialize the manager var audioManager = new AudioStreamingManager(networkClient); // Start streaming an AudioClip audioManager.StartAudioStream(myAudioClip, "my_stream"); // Update regularly (e.g., in Unity Update) audioManager.Update(); // Stop streaming audioManager.StopAudioStream("my_stream"); ``` -------------------------------- ### Build SteamNetworkLib for Il2cpp Runtime Source: https://github.com/ifbars/steamnetworklib/blob/master/README.md Builds the SteamNetworkLib project for the Il2cpp runtime. Ensure you have the correct .NET Framework version installed. ```powershell dotnet build -c Il2cpp ``` -------------------------------- ### Build SteamNetworkLib for Mono Runtime Source: https://github.com/ifbars/steamnetworklib/blob/master/README.md Builds the SteamNetworkLib project for the Mono runtime. Ensure you have the correct .NET Framework version installed. ```powershell dotnet build -c Mono ``` -------------------------------- ### Configure MonoGame and Steamworks.NET Paths Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Specify the installation paths for MonoGame and the location of the Steamworks.NET DLL in your user-specific project properties. ```xml D:\Path\To\Your\Game $(MonoGameInstallPath)\Schedule I_Data\Managed ``` -------------------------------- ### KeyPrefix: Avoiding Collisions with Custom Prefixes Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Always use a unique prefix for your mod's SyncVars to prevent key collisions with other mods. This example shows how to apply the prefix during SyncVar creation and manual data setting. ```csharp // Good: Use your mod name as prefix var options = new NetworkSyncOptions { KeyPrefix = "MyMod_" }; var score = client.CreateHostSyncVar("Score", 0, options); // Actual Steam key: "MyMod_Score" var teamName = client.CreateClientSyncVar("TeamName", "Alpha", options); // Actual Steam key: "MyMod_TeamName" ``` ```csharp const string PREFIX = "MyMod_"; client.SetLobbyData($"{PREFIX}version", "1.0.0"); client.SetMyData($"{PREFIX}loadout", "1911"); ``` -------------------------------- ### Send and Receive Custom Messages Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/p2p-messaging.md Send and broadcast custom messages after defining and registering them. This example shows sending a TransactionMessage to a specific player or broadcasting it. ```csharp // Send a custom message var transaction = new TransactionMessage { TransactionId = "txn-12345", FromPlayer = "Player1", ToPlayer = "Player2", Amount = 100.00m, Currency = "USD" }; await client.SendMessageToPlayerAsync(targetId, transaction); // Or broadcast to all players client.BroadcastMessage(transaction); ``` -------------------------------- ### Set and Get Lobby Data (Host/Client) Source: https://context7.com/ifbars/steamnetworklib/llms.txt The host can set lobby-wide key-value data, which all clients can read. Changes trigger the OnLobbyDataChanged event. ```csharp // Host sets values if (_client.IsHost) { _client.SetLobbyData("map", "downtown"); _client.SetLobbyData("gameMode", "coop"); _client.SetLobbyData("version", "1.2.0"); } // Any client reads values string? map = _client.GetLobbyData("map"); // "downtown" string? mode = _client.GetLobbyData("gameMode"); // "coop" // React to remote changes _client.OnLobbyDataChanged += (_, e) => { MelonLogger.Msg($"Lobby data: [{e.Key}] '{e.OldValue}' -> '{e.NewValue}' (by {e.ChangedBy})"); if (e.Key == "map") LoadMap(e.NewValue); }; ``` -------------------------------- ### Set and Get Lobby-Wide Data Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md Lobby owners can set lobby-wide data using `SetLobbyData`, and anyone can read it using `GetLobbyData`. ```csharp // Set by the lobby owner client.SetLobbyData("mod_version", "1.0.0"); // Read by anyone string modVersion = client.GetLobbyData("mod_version"); ``` -------------------------------- ### Implement Audio Streaming Channel Source: https://context7.com/ifbars/steamnetworklib/llms.txt Concrete implementation for audio streaming using float arrays. Requires implementing DeserializeFrame and HandleMissingFrame. Setup involves configuring buffer sizes and enabling features like packet loss detection. ```csharp public class AudioChannel : StreamChannel { public AudioChannel(string id) : base(id) { } protected override float[]? DeserializeFrame(byte[] data, StreamMessage msg) { // Decode bytes -> PCM float samples (e.g. via OpusSharp) return DecodeOpus(data); } protected override float[]? HandleMissingFrame(uint seq) { // Packet-loss concealment: repeat last frame or return silence return new float[960]; // 20ms of silence at 48kHz } } ``` ```csharp // Setup var channel = new AudioChannel("voice_chat"); channel.BufferMs = 150; // 150ms jitter buffer channel.MaxBufferMs = 400; channel.EnablePacketLossDetection = true; channel.EnableJitterBuffer = true; channel.OnFrameReady += pcm => AudioOutput.Write(pcm); channel.OnStreamStarted += _ => MelonLogger.Msg("Stream started"); channel.OnStreamEnded += _ => MelonLogger.Msg("Stream ended"); channel.OnFrameDropped += seq => MelonLogger.Msg($"Frame {seq} dropped"); channel.OnFrameLate += (seq, late) => MelonLogger.Msg($"Frame {seq} late by {late.TotalMilliseconds:F0}ms"); // Route incoming StreamMessages to the channel _client.RegisterMessageHandler((msg, sender) => channel.ProcessStreamMessage(msg, sender)); // Call every frame public override void OnUpdate() { _client?.ProcessIncomingMessages(); channel.Update(); // drains jitter buffer } // Stats MelonLogger.Msg($"Received={channel.TotalFramesReceived} Dropped={channel.TotalFramesDropped} Buffered={channel.BufferedFrameCount}"); channel.Reset(); // clear buffers and counters channel.Dispose(); // cleanup ``` -------------------------------- ### Add Video Streaming Channel Source: https://github.com/ifbars/steamnetworklib/blob/master/Examples/AudioStreamingExample/README.md Example of extending the architecture by adding a video streaming channel. This involves creating a new class that inherits from `StreamChannel` and implements video decoding. ```csharp public class H264VideoStreamChannel : StreamChannel { protected override byte[]? DeserializeFrame(byte[] data, StreamMessage message) { // Decode H.264 frame return DecodeH264(data); } } ``` -------------------------------- ### Add Sensor Data Sender Source: https://github.com/ifbars/steamnetworklib/blob/master/Examples/AudioStreamingExample/README.md Example of extending the architecture by adding a sensor data sender. This involves creating a new class that inherits from `StreamSender` and implements data serialization. ```csharp public class SensorDataSender : StreamSender { protected override byte[]? SerializeFrame(SensorReading data) { // Serialize sensor data (JSON, MessagePack, etc.) return JsonSerializer.SerializeToUtf8Bytes(data); } } ``` -------------------------------- ### Minimal MelonLoader Mod Setup with SteamNetworkLib Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/getting-started.md Implement the basic structure for a MelonLoader mod using SteamNetworkLib, including client initialization, message processing, and cleanup. ```csharp using MelonLoader; using SteamNetworkLib; public class YourAwesomeModMain : MelonMod { private SteamNetworkClient client; public override void OnInitializeMelon() { // Optional: configure network rules (relay, session policy, channels) var rules = new SteamNetworkLib.Core.NetworkRules { EnableRelay = true, AcceptOnlyFriends = false }; client = new SteamNetworkClient(rules); if (client.Initialize()) { // Optional: subscribe to events client.OnLobbyCreated += (s, e) => MelonLogger.Msg($"Lobby: {e.Lobby.LobbyId}"); } } public override void OnUpdate() { client?.ProcessIncomingMessages(); } public override void OnDeinitializeMelon() { client?.Dispose(); } } ``` -------------------------------- ### Custom Type Example for SyncVars Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Define and use custom serializable classes or structs for SyncVars, ensuring they meet specific requirements for constructors, properties, and types. ```csharp public class GameSettings { public int MaxPlayers { get; set; } = 4; public string MapName { get; set; } = "default"; public bool FriendlyFire { get; set; } = false; public List EnabledMods { get; set; } = new(); } // Usage var settings = client.CreateHostSyncVar("Settings", new GameSettings()); ``` -------------------------------- ### Set and Get Per-Player Data Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md Players can set their own visible data using `SetMyData` and read it with `GetMyData`. `GetPlayerData` reads data for a specific player, and `GetDataForAllPlayers` retrieves the same key for all players. ```csharp // Local player sets their visible data client.SetMyData("name", "bob"); // Read for self string myClass = client.GetMyData("name"); // Read for any specific player string otherClass = client.GetPlayerData(playerId, "name"); // Read the same key for everyone Dictionary allClasses = client.GetDataForAllPlayers("name"); ``` -------------------------------- ### Send File Transfer Message in Chunks Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/p2p-messaging.md Send files by breaking them into chunks up to the maximum packet size. This example reads a file, iterates through its bytes, and sends each chunk as a FileTransferMessage. ```csharp var bytes = File.ReadAllBytes(path); int chunkSize = client.P2PManager.MaxPacketSize; // use client wrappers for sending int total = (int)Math.Ceiling((double)bytes.Length / chunkSize); for (int i = 0; i < total; i++) { var slice = bytes.Skip(i * chunkSize).Take(chunkSize).ToArray(); var file = new FileTransferMessage { FileName = Path.GetFileName(path), FileSize = bytes.Length, ChunkIndex = i, TotalChunks = total, IsFileData = true, ChunkData = slice }; await client.SendMessageToPlayerAsync(targetId, file); } ``` -------------------------------- ### Set and Get Player Data (Per-Player) Source: https://context7.com/ifbars/steamnetworklib/llms.txt Players can set their own member data, and all players can read any player's data. Changes trigger the OnMemberDataChanged event. Batch setting is also supported. ```csharp // Local player sets own data _client.SetMyData("character", "engineer"); _client.SetMyData("level", "42"); // Batch-set multiple keys at once _client.SetMyDataBatch(new Dictionary { ["character"] = "medic", ["level"] = "55", ["ready"] = "true" }); // Read another player's data string? theirChar = _client.GetPlayerData(somePlayerId, "character"); // Read the same key for all players (e.g., for a scoreboard) Dictionary levels = _client.GetDataForAllPlayers("level"); foreach (var kv in levels) MelonLogger.Msg($" {kv.Key}: level {kv.Value}"); // React to changes _client.OnMemberDataChanged += (_, e) => MelonLogger.Msg($"{e.MemberId} changed [{e.Key}]: '{e.OldValue}' -> '{e.NewValue}'"); ``` -------------------------------- ### Manage Lobby-Wide Data Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/advanced-api.md Get or set key-value data that is accessible to all members of the lobby. ```csharp // Lobby‑wide var map = client.LobbyData.GetData("map"); client.LobbyData.SetData("map", "arena"); ``` -------------------------------- ### Initialize Network Client with Rules Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/network-rules.md Set up the NetworkRules object with desired configurations like enabling relay and specifying friend-only connections, then initialize the SteamNetworkClient. ```csharp using SteamNetworkLib; using SteamNetworkLib.Core; var rules = new NetworkRules { EnableRelay = true, // Use Steam relay for NAT traversal AcceptOnlyFriends = false, // Accept sessions from anyone }; var client = new SteamNetworkClient(rules); client.Initialize(); ``` -------------------------------- ### P2P Manager - Packet Limits Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/advanced-api.md Get the maximum packet size allowed for P2P communication. ```APIDOC ## P2P Manager - Packet Limits ### Description Get the maximum packet size allowed for P2P communication. ### Property `MaxPacketSize` ### Usage Ensure your chunks are less than or equal to this value. ### Request Example ```csharp int max = p2p.MaxPacketSize; // keep chunks <= max ``` ``` -------------------------------- ### Build Project with Specific Configurations Source: https://github.com/ifbars/steamnetworklib/blob/master/CONTRIBUTING.md Use these commands to build the project for Mono or IL2CPP runtimes. ```bash dotnet build -c Mono ``` ```bash dotnet build -c Il2cpp ``` -------------------------------- ### Build and Run SteamNetworkLib Tests Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Commands to build the test project and execute the tests from the repository root. ```bash # From repository root dotnet build tests/SteamNetworkLib.Tests/SteamNetworkLib.Tests.csproj dotnet test tests/SteamNetworkLib.Tests/SteamNetworkLib.Tests.csproj ``` -------------------------------- ### Initialize SteamNetworkClient and Set Lobby Data Source: https://github.com/ifbars/steamnetworklib/blob/master/index.md Demonstrates basic initialization of the SteamNetworkClient and setting lobby data. Ensure the SteamNetworkClient is initialized before using its methods. ```csharp // Simple, clean API var steamNetwork = new SteamNetworkClient(); steamNetwork.Initialize(); // Easy data management steamNetwork.SetLobbyData("mod_version", "1.0.0"); ``` -------------------------------- ### Initialize Core Components Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/advanced-api.md Access core components like LobbyManager and P2PManager after initializing the SteamNetworkClient with specific rules. ```csharp var client = new SteamNetworkClient(rules); client.Initialize(); var lobby = client.LobbyManager; var p2p = client.P2PManager; ``` -------------------------------- ### Get Maximum P2P Packet Size Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/advanced-api.md Retrieve the maximum allowed packet size for P2P communication. Ensure sent chunks do not exceed this limit. ```csharp int max = p2p.MaxPacketSize; // keep chunks <= max ``` -------------------------------- ### Version Compatibility Helper Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md The client checks library version compatibility by default. `OnVersionMismatch` is triggered on differences. Version checking can be toggled, and manual checks are available. ```csharp client.OnVersionMismatch += (s, e) => { MelonLogger.Warning($"Version mismatch. Local: {e.LocalVersion}"); }; // Optional: toggle client.VersionCheckEnabled = true; // Manual check bool ok = client.CheckLibraryVersionCompatibility(); ``` -------------------------------- ### SteamNetworkClient Initialization and Lifecycle Source: https://context7.com/ifbars/steamnetworklib/llms.txt The SteamNetworkClient is the main entry point for the library. It manages sub-systems for lobby and P2P communication. Initialize it with optional NetworkRules and dispose of it on mod shutdown. ```APIDOC ## SteamNetworkClient - Initialization and Lifecycle ### Description Initializes and manages the core networking components. It should be disposed of when the mod shuts down. ### Method ```csharp public SteamNetworkClient(NetworkRules? rules = null) public void Initialize() public void Dispose() ``` ### Usage Example ```csharp using SteamNetworkLib; using SteamNetworkLib.Core; using SteamNetworkLib.Models; using MelonLoader; public class MyMod : MelonMod { private SteamNetworkClient? _client; public override void OnLateInitializeMelon() { var rules = new NetworkRules { EnableRelay = true, AcceptOnlyFriends = false, DefaultSendType = EP2PSend.k_EP2PSendReliable, MinReceiveChannel = 0, MaxReceiveChannel = 3 }; _client = new SteamNetworkClient(rules); try { _client.Initialize(); MelonLogger.Msg($"SteamNetworkLib {SteamNetworkClient.LibraryVersion} ready"); } catch (SteamNetworkException ex) { MelonLogger.Error($"Init failed: {ex.Message}"); } _client.OnLobbyCreated += (_, e) => MelonLogger.Msg($"Lobby created: {e.Lobby.LobbyId}"); _client.OnLobbyJoined += (_, e) => MelonLogger.Msg($"Joined lobby: {e.Lobby.LobbyId}"); _client.OnLobbyLeft += (_, e) => MelonLogger.Msg($"Left lobby"); _client.OnMemberJoined += (_, e) => MelonLogger.Msg($"{e.Member.DisplayName} joined"); _client.OnMemberLeft += (_, e) => MelonLogger.Msg($"{e.Member.DisplayName} left"); _client.OnVersionMismatch += (_, e) => MelonLogger.Warning($"Library version mismatch! Incompatible players: {e.IncompatiblePlayers.Count}"); } public override void OnUpdate() => _client?.ProcessIncomingMessages(); public override void OnApplicationQuit() => _client?.Dispose(); } ``` ``` -------------------------------- ### Run All Tests with .NET CLI Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Execute all tests within the project using the 'dotnet test' command. ```bash dotnet test ``` -------------------------------- ### Configure NetworkSyncOptions Source: https://context7.com/ifbars/steamnetworklib/llms.txt Set synchronization options for HostSyncVar and ClientSyncVar. Defaults are provided for most options. ```csharp var opts = new NetworkSyncOptions { AutoSync = true, // immediately propagate writes (default: true) SyncOnPlayerJoin = true, // re-broadcast to late-joiners (default: true) MaxSyncsPerSecond = 10, // rate limiter, 0 = unlimited (default: 0) ThrowOnValidationError = false, // false = log+event, true = throw (default: false) WarnOnIgnoredWrites = false, // warn when non-host writes HostSyncVar (default: false) KeyPrefix = "Mod_", // prefixed to avoid key collisions (default: null) Serializer = null // null = default JsonSyncSerializer }; // Use with CreateHostSyncVar / CreateClientSyncVar var score = _client.CreateHostSyncVar("Score", 0, opts); ``` -------------------------------- ### Configuration Options with NetworkSyncOptions Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Explains how to customize the behavior of synchronized variables using the `NetworkSyncOptions` class. ```APIDOC ## Configuration Options Customize behavior with `NetworkSyncOptions`: ```csharp var options = new NetworkSyncOptions { // Log warnings when non-host tries to write (debugging) WarnOnIgnoredWrites = true, // Add prefix to avoid key collisions with other mods (IMPORTANT!) KeyPrefix = "MyMod_", // Disable auto-sync for manual batching (see below) AutoSync = false, // Rate limit syncs (e.g., 10 per second for position updates) MaxSyncsPerSecond = 10, // Throw exceptions on validation errors (default: false) ThrowOnValidationError = false, // Use custom serializer (optional) Serializer = new MyCustomSerializer() }; var score = client.CreateHostSyncVar("Score", 0, options); ``` ### KeyPrefix - Avoid Collisions **Always use a unique prefix for published mods to prevent key collisions with other mods.** ```csharp // Good: Use your mod name as prefix var options = new NetworkSyncOptions { KeyPrefix = "MyMod_" }; var score = client.CreateHostSyncVar("Score", 0, options); // Actual Steam key: "MyMod_Score" var teamName = client.CreateClientSyncVar("TeamName", "Alpha", options); // Actual Steam key: "MyMod_TeamName" ``` When using raw lobby/member data, apply prefixes manually: ```csharp const string PREFIX = "MyMod_"; client.SetLobbyData($"{PREFIX}version", "1.0.0"); client.SetMyData($"{PREFIX}loadout", "1911"); ``` ``` -------------------------------- ### Create and Sync Host Variable Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/recipes.md Use this to create a variable on the host that synchronizes its value to all clients. Subscribe to value changes to react to updates. ```csharp var roundNumber = client.CreateHostSyncVar("Round", 1); // Subscribe to changes roundNumber.OnValueChanged += (oldVal, newVal) => { MelonLogger.Msg($"Round {oldVal} -> {newVal}"); }; // Host sets new value (clients only read) roundNumber.Value = 2; ``` -------------------------------- ### Project Configuration for SteamNetworkLib Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/getting-started.md Configure your Unity project's .csproj file to target .NET Standard 2.1 and include necessary references for SteamNetworkLib. ```xml netstandard2.1 YourAwesomeMod 1.0.0 ``` -------------------------------- ### SteamNetworkLib Architecture Overview Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/introduction.md Illustrates the modular design of SteamNetworkLib, highlighting its main components and their responsibilities. ```text SteamNetworkClient (Main Entry Point) ├── SteamLobbyManager (Lobby operations) ├── SteamLobbyData (Lobby-wide data) ├── SteamMemberData (Player-specific data) └── SteamP2PManager (Peer-to-peer communication) ``` -------------------------------- ### Define Custom Transaction Message Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/p2p-messaging.md Create a custom message type by inheriting from P2PMessage. Implement MessageType, Serialize, and Deserialize methods. This example defines a TransactionMessage with transaction details. ```csharp using System.Text; using SteamNetworkLib.Models; public class TransactionMessage : P2PMessage { public override string MessageType => "TRANSACTION"; public string TransactionId { get; set; } = string.Empty; public string FromPlayer { get; set; } = string.Empty; public string ToPlayer { get; set; } = string.Empty; public decimal Amount { get; set; } public string Currency { get; set; } = "USD"; public override byte[] Serialize() { var json = System.Text.Json.JsonSerializer.Serialize(this); return Encoding.UTF8.GetBytes(json); } public override void Deserialize(byte[] data) { var json = Encoding.UTF8.GetString(data); var deserialized = System.Text.Json.JsonSerializer.Deserialize(json); if (deserialized != null) { TransactionId = deserialized.TransactionId; FromPlayer = deserialized.FromPlayer; ToPlayer = deserialized.ToPlayer; Amount = deserialized.Amount; Currency = deserialized.Currency; SenderId = deserialized.SenderId; Timestamp = deserialized.Timestamp; } } ``` -------------------------------- ### Run Integration Tests with Filter Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Execute only the integration tests, which require Goldberg and Steam API simulation, by filtering on 'Integration'. ```bash dotnet test --filter "FullyQualifiedName~Integration" ``` -------------------------------- ### Run Tests with Verbose Output using .NET CLI Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Enable detailed logging during test execution for better debugging. ```bash dotnet test --logger "console;verbosity=detailed" ``` -------------------------------- ### Lobby Manager - Create/Join Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/advanced-api.md Create a new lobby or join an existing one. ```APIDOC ## Lobby Manager - Create/Join ### Description Create a new lobby or join an existing one. ### Method `CreateLobbyAsync`, `JoinLobbyAsync` ### Parameters #### CreateLobbyAsync - **lobbyType** (ELobbyType) - The type of lobby to create (e.g., k_ELobbyTypeFriendsOnly). - **maxMembers** (int) - The maximum number of members allowed in the lobby. #### JoinLobbyAsync - **lobbyId** (CSteamID) - The ID of the lobby to join. ### Request Example ```csharp var lobbyInfo = await lobby.CreateLobbyAsync(ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers: 4); // await lobby.JoinLobbyAsync(lobbyId); ``` ``` -------------------------------- ### Create and Sync Per-Client Variable Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/recipes.md Use this to create a variable that each client can manage independently, with changes visible to all. Useful for player-specific states like readiness. ```csharp var isReady = client.CreateClientSyncVar("Ready", false); // Subscribe to any player's changes isReady.OnValueChanged += (playerId, oldVal, newVal) => { MelonLogger.Msg($"Player {playerId}: ready={newVal}"); }; // Set my own ready status isReady.Value = true; // Check if everyone is ready var allReady = isReady.GetAllValues().Values.All(r => r); ``` -------------------------------- ### Git Branching and Committing Source: https://github.com/ifbars/steamnetworklib/blob/master/CONTRIBUTING.md Follow these commands for creating a feature branch, committing changes, and pushing to your fork. ```bash git checkout -b feature/amazing-feature ``` ```bash git commit -m 'Add amazing feature' ``` ```bash git push origin feature/amazing-feature ``` -------------------------------- ### Run a Specific Test with .NET CLI Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Execute a single, specific test case by providing its fully qualified name. ```bash dotnet test --filter "HostSyncVar_HostSetsValue_ClientReceivesUpdate" ``` -------------------------------- ### Create and Join Lobbies Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/advanced-api.md Create a new lobby with specified type and maximum members, or join an existing lobby using its ID. ```csharp // Create / Join var lobbyInfo = await lobby.CreateLobbyAsync(ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers: 4); // await lobby.JoinLobbyAsync(lobbyId); ``` -------------------------------- ### Create Per-Client Synchronized Variables (ClientSyncVar) Source: https://context7.com/ifbars/steamnetworklib/llms.txt ClientSyncVar stores each client's own value in Steam member data, readable by all clients. Each client can write their own slice. Use OnValueChanged to subscribe to any player's changes and OnMyValueChanged for local player changes. ```csharp var opts = new NetworkSyncOptions { KeyPrefix = "MyMod_" }; ClientSyncVar isReady = _client.CreateClientSyncVar("Ready", defaultValue: false, opts); ClientSyncVar teamChoice = _client.CreateClientSyncVar("Team", defaultValue: "none", opts); // Subscribe to any player's changes isReady.OnValueChanged += (playerId, old, @new) => MelonLogger.Msg($"{playerId} ready: {@new}"); // Subscribe only to local player's changes isReady.OnMyValueChanged += (old, @new) => MelonLogger.Msg($"My ready: {@new}"); // Set my own value isReady.Value = true; teamChoice.Value = "blue"; // Read another player's value bool theirReady = isReady.GetValue(otherPlayerId); // returns defaultValue if not set // Get all players' values (e.g., to check if everyone is ready) Dictionary allReady = isReady.GetAllValues(); bool everyoneReady = allReady.Count > 0 && allReady.Values.All(r => r); // Refresh cache from Steam isReady.Refresh(); ``` -------------------------------- ### Run Unit Tests with Filter Source: https://github.com/ifbars/steamnetworklib/blob/master/tests/SETUP_GUIDE.md Execute only the unit tests, which do not require Steam or Goldberg, by filtering on 'Unit'. ```bash dotnet test --filter "FullyQualifiedName~Unit" ``` -------------------------------- ### Open Steam Overlay Invite Dialog Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/recipes.md Opens the Steam overlay's invite dialog, allowing the user to invite friends to the game session. ```csharp client.OpenInviteDialog(); ``` -------------------------------- ### Initialize SteamNetworkClient and Subscribe to Events Source: https://context7.com/ifbars/steamnetworklib/llms.txt Initializes the SteamNetworkClient with optional network rules and subscribes to essential lobby and member events. Ensure ProcessIncomingMessages is called every frame and Dispose on mod shutdown. ```csharp using SteamNetworkLib; using SteamNetworkLib.Core; using SteamNetworkLib.Models; using MelonLoader; public class MyMod : MelonMod { private SteamNetworkClient? _client; public override void OnLateInitializeMelon() { // Optional: configure network behaviour before init var rules = new NetworkRules { EnableRelay = true, // allow Steam relay for NAT traversal AcceptOnlyFriends = false, // accept sessions from all lobby members DefaultSendType = EP2PSend.k_EP2PSendReliable, MinReceiveChannel = 0, MaxReceiveChannel = 3 }; _client = new SteamNetworkClient(rules); try { _client.Initialize(); MelonLogger.Msg($"SteamNetworkLib {SteamNetworkClient.LibraryVersion} ready"); } catch (SteamNetworkException ex) { MelonLogger.Error($"Init failed: {ex.Message}"); } // Subscribe before creating/joining lobby _client.OnLobbyCreated += (_, e) => MelonLogger.Msg($"Lobby created: {e.Lobby.LobbyId}"); _client.OnLobbyJoined += (_, e) => MelonLogger.Msg($"Joined lobby: {e.Lobby.LobbyId}"); _client.OnLobbyLeft += (_, e) => MelonLogger.Msg($"Left lobby"); _client.OnMemberJoined += (_, e) => MelonLogger.Msg($"{e.Member.DisplayName} joined"); _client.OnMemberLeft += (_, e) => MelonLogger.Msg($"{e.Member.DisplayName} left"); _client.OnVersionMismatch += (_, e) => MelonLogger.Warning($"Library version mismatch! Incompatible players: {e.IncompatiblePlayers.Count}"); } public override void OnUpdate() => _client?.ProcessIncomingMessages(); // must be called every frame public override void OnApplicationQuit() => _client?.Dispose(); } ``` -------------------------------- ### CreateLobbyAsync / JoinLobbyAsync / LeaveLobby Source: https://context7.com/ifbars/steamnetworklib/llms.txt Methods for managing the Steam lobby lifecycle. These operations have a 10-second timeout and throw LobbyException on failure. ```APIDOC ## CreateLobbyAsync / JoinLobbyAsync / LeaveLobby ### Description Asynchronous methods for creating, joining, and leaving Steam lobbies. Includes functionality to inspect lobby members and open the Steam invite dialog. ### Methods ```csharp public async Task CreateLobbyAsync(ELobbyType type, int maxMembers) public async Task JoinLobbyAsync(CSteamID lobbyId) public void LeaveLobby() public void OpenInviteDialog() public List GetLobbyMembers() ``` ### Usage Example ```csharp // Create a friends-only lobby for up to 6 players try { LobbyInfo lobby = await _client.CreateLobbyAsync( ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers: 6); MelonLogger.Msg($"Lobby {lobby.LobbyId} created, owner: {lobby.OwnerId}"); } catch (LobbyException ex) { MelonLogger.Error(ex.Message); } // Join an existing lobby by its CSteamID CSteamID targetLobby = new CSteamID(/* lobby id from invite */); try { LobbyInfo lobby = await _client.JoinLobbyAsync(targetLobby); MelonLogger.Msg($"Joined: {lobby.MemberCount}/{lobby.MaxMembers} members"); } catch (LobbyException ex) { MelonLogger.Error(ex.Message); } // Inspect members List members = _client.GetLobbyMembers(); foreach (var m in members) MelonLogger.Msg($" {m.DisplayName} (host={m.IsOwner}, local={m.IsLocalPlayer})"); // Open the Steam overlay invite dialog _client.OpenInviteDialog(); // Leave _client.LeaveLobby(); ``` ``` -------------------------------- ### Create, Join, and Leave Steam Lobbies Source: https://context7.com/ifbars/steamnetworklib/llms.txt Asynchronous methods for managing Steam lobbies. Includes options for creating lobbies with specific types and member limits, joining existing lobbies by ID, inspecting members, and leaving. ```csharp // Create a friends-only lobby for up to 6 players try { LobbyInfo lobby = await _client.CreateLobbyAsync( ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers: 6); MelonLogger.Msg($"Lobby {lobby.LobbyId} created, owner: {lobby.OwnerId}"); } catch (LobbyException ex) { MelonLogger.Error(ex.Message); } // Join an existing lobby by its CSteamID CSteamID targetLobby = new CSteamID(/* lobby id from invite */); try { LobbyInfo lobby = await _client.JoinLobbyAsync(targetLobby); MelonLogger.Msg($"Joined: {lobby.MemberCount}/{lobby.MaxMembers} members"); } catch (LobbyException ex) { MelonLogger.Error(ex.Message); } // Inspect members List members = _client.GetLobbyMembers(); foreach (var m in members) MelonLogger.Msg($" {m.DisplayName} (host={m.IsOwner}, local={m.IsLocalPlayer})"); // Open the Steam overlay invite dialog _client.OpenInviteDialog(); // Leave _client.LeaveLobby(); ``` -------------------------------- ### Perform Batch Updates for Player Data Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md Use `SetMyDataBatch` to efficiently update multiple player data keys at once with a dictionary. ```csharp client.SetMyDataBatch(new Dictionary { ["loadout"] = "1911", ["ready"] = "true", }); ``` -------------------------------- ### Sync Mod Data and Check Compatibility with SteamNetworkLib Source: https://context7.com/ifbars/steamnetworklib/llms.txt Use SyncModDataWithAllPlayersAsync to publish local mod version data and broadcast it via P2P. IsModDataCompatible checks if all players have the same version. GetDataForAllPlayers retrieves all versions for inspection. ```csharp private const string COMPAT_KEY = "my_mod_v"; private async Task OnJoinedLobby() { // Publish local version to member data AND broadcast via P2P in one call await _client.SyncModDataWithAllPlayersAsync(COMPAT_KEY, "1.3.0", dataType: "string"); // Check that every player has the same value bool allCompatible = _client.IsModDataCompatible(COMPAT_KEY); if (!allCompatible) { // Inspect who has a different version var versions = _client.GetDataForAllPlayers(COMPAT_KEY); foreach (var kv in versions) MelonLogger.Warning($" {kv.Key}: {kv.Value}"); } } // Automatically fires when a version mismatch is detected _client.OnVersionMismatch += (_, e) => { MelonLogger.Warning($"SteamNetworkLib version mismatch! Local: {e.LocalVersion}"); foreach (var p in e.IncompatiblePlayers) MelonLogger.Warning($" Incompatible: {p}"); }; // Runtime version check bool ok = _client.CheckLibraryVersionCompatibility(); Dictionary libVersions = _client.GetPlayerLibraryVersions(); ``` -------------------------------- ### Lobby Event Handling Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/lobby-management.md Subscribe to events for lobby creation, joining, leaving, and member changes. Ensure you have the necessary MelonLogger for output. ```csharp client.OnLobbyCreated += (s, e) => { MelonLogger.Msg($"Lobby created: {e.Lobby.LobbyId}"); }; client.OnLobbyJoined += (s, e) => { MelonLogger.Msg($"Joined lobby: {e.Lobby.LobbyId}"); }; client.OnLobbyLeft += (s, e) => { MelonLogger.Msg($"Left lobby {e.LobbyId}: {e.Reason}"); }; client.OnMemberJoined += (s, e) => { MelonLogger.Msg($"Member joined: {e.Member.DisplayName}"); }; client.OnMemberLeft += (s, e) => { MelonLogger.Msg($"Member left: {e.Member.DisplayName}"); }; ``` -------------------------------- ### Configuration Options for NetworkSync Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Customize SyncVar behavior using NetworkSyncOptions, including warning on ignored writes, setting a key prefix, disabling auto-sync, rate limiting, and error handling. ```csharp var options = new NetworkSyncOptions { // Log warnings when non-host tries to write (debugging) WarnOnIgnoredWrites = true, // Add prefix to avoid key collisions with other mods (IMPORTANT!) KeyPrefix = "MyMod_", // Disable auto-sync for manual batching (see below) AutoSync = false, // Rate limit syncs (e.g., 10 per second for position updates) MaxSyncsPerSecond = 10, // Throw exceptions on validation errors (default: false) ThrowOnValidationError = false, // Use custom serializer (optional) Serializer = new MyCustomSerializer() }; var score = client.CreateHostSyncVar("Score", 0, options); ``` -------------------------------- ### Use Unique Prefixes for Data Keys Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md Always use custom prefixes for your mod's data keys to prevent collisions with other mods. Generic keys may conflict. ```csharp // Good: Use a unique prefix for your mod const string PREFIX = "MyMod_"; client.SetLobbyData($"{PREFIX}version", "1.0.0"); client.SetMyData($"{PREFIX}loadout", "1911"); // Bad: Generic keys may collide with other mods client.SetLobbyData("version", "1.0.0"); // May conflict! client.SetMyData("loadout", "1911"); // May conflict! ``` -------------------------------- ### SyncVars Handle Prefixes Automatically Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md SyncVars automatically handle prefixes when `KeyPrefix` is set in `NetworkSyncOptions`, simplifying key management. ```csharp var options = new NetworkSyncOptions { KeyPrefix = "MyMod_" }; var score = client.CreateHostSyncVar("Score", 0, options); // Actual Steam key: "MyMod_Score" - no manual prefix needed! ``` -------------------------------- ### Mod Compatibility Helpers Source: https://context7.com/ifbars/steamnetworklib/llms.txt Provides methods for synchronizing mod version data across all lobby members and checking compatibility. It includes functions to sync data, check if all players have compatible versions, and retrieve version data for all players. It also includes an event for version mismatches and a method to check library version compatibility. ```APIDOC ## SyncModDataWithAllPlayersAsync / IsModDataCompatible — Mod Compatibility Helpers ### Description High-level helpers for syncing and verifying mod version data across all lobby members. ### Methods - `SyncModDataWithAllPlayersAsync(string key, string value, string dataType)`: Publishes local version to member data and broadcasts via P2P. - `IsModDataCompatible(string key)`: Checks if all players have the same value for the given key. - `GetDataForAllPlayers(string key)`: Retrieves a dictionary of player IDs and their corresponding data for the given key. - `CheckLibraryVersionCompatibility()`: Checks for compatibility of the SteamNetworkLib library version across players. - `GetPlayerLibraryVersions()`: Retrieves a dictionary of player IDs and their library versions. ### Events - `OnVersionMismatch`: Automatically fires when a version mismatch is detected, providing details about the mismatch and incompatible players. ``` -------------------------------- ### ClientSyncVar: Per-Client Data Creation and Usage Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/sync-vars.md Employ ClientSyncVar for values unique to each client. Modify your own value and read others'. Subscribe to individual or all client changes. ```csharp // Create var isReady = client.CreateClientSyncVar("Ready", false); var playerLoadout = client.CreateClientSyncVar("Loadout", "default"); // Subscribe to any client's changes isReady.OnValueChanged += (playerId, oldVal, newVal) => { MelonLogger.Msg($"Player {playerId} ready: {newVal}"); }; // Subscribe to only my changes isReady.OnMyValueChanged += (oldVal, newVal) => { MelonLogger.Msg("I am now ready: {newVal}"); }; // Set my own value isReady.Value = true; // Read my value bool myReady = isReady.Value; // Read another client's value bool player2Ready = isReady.GetValue(player2Id); // Get all clients' values Dictionary allReady = isReady.GetAllValues(); bool everyoneReady = allReady.Values.All(r => r); ``` -------------------------------- ### Handle Lobby Data Changes Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md Subscribe to `OnLobbyDataChanged` to log changes to lobby-wide data, including the key and new value. ```csharp client.OnLobbyDataChanged += (s, e) => { MelonLogger.Msg($"Lobby data: {e.Key} -> {e.NewValue}"); }; ``` -------------------------------- ### Configure NetworkRules for P2P Source: https://context7.com/ifbars/steamnetworklib/llms.txt Control P2P behavior including relay, session acceptance, and message routing. Rules are passed to the SteamNetworkClient constructor or updated at runtime. ```csharp var rules = new NetworkRules { EnableRelay = true, // Steam relay for NAT-traversal AcceptOnlyFriends = false, // allow all lobby members DefaultSendType = EP2PSend.k_EP2PSendReliable, MinReceiveChannel = 0, MaxReceiveChannel = 3, // Per-message policy: route stream messages to channel 1 unreliable MessagePolicy = msg => msg is StreamMessage ? (1, EP2PSend.k_EP2PSendUnreliableNoDelay) : (0, EP2PSend.k_EP2PSendReliable) }; var client = new SteamNetworkClient(rules); client.Initialize(); // Update rules after initialization (changes relay setting immediately) client.UpdateNetworkRules(new NetworkRules { EnableRelay = false }); ``` -------------------------------- ### Send Screenshot File in Chunks Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/recipes.md Send a file, such as a screenshot, to another player by breaking it into manageable chunks. This handles large files by sending them piece by piece. ```csharp var bytes = File.ReadAllBytes("screenshot.png"); int chunk = client.P2PManager.MaxPacketSize; int total = (int)Math.Ceiling((double)bytes.Length / chunk); for (int i = 0; i < total; i++) { var slice = bytes.Skip(i * chunk).Take(chunk).ToArray(); var msg = new FileTransferMessage { FileName = "screenshot.png", FileSize = bytes.Length, ChunkIndex = i, TotalChunks = total, IsFileData = true, ChunkData = slice }; await client.SendMessageToPlayerAsync(hostId, msg, channel: 1); } ``` -------------------------------- ### Handle Member Data Changes Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/data-synchronization.md Subscribe to `OnMemberDataChanged` to log changes to per-player data, including the member ID, key, and new value. ```csharp client.OnMemberDataChanged += (s, e) => { MelonLogger.Msg($"Member {e.MemberId}: {e.Key} -> {e.NewValue}"); }; ``` -------------------------------- ### Check Mod Version Compatibility Source: https://github.com/ifbars/steamnetworklib/blob/master/docs/recipes.md Set and synchronize mod version data with all players. Use this to check if all players are running compatible versions of the mod. ```csharp client.SetMyData("mod_version", MyMod.Version); client.SyncModDataWithAllPlayers("mod_version", MyMod.Version); if (!client.IsModDataCompatible("mod_version")) { MelonLogger.Warning("Players have mismatched mod versions"); } ```