first commit

This commit is contained in:
2025-12-13 14:28:35 +01:00
commit 679c3c9a52
113 changed files with 715750 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
// The SteamManager is designed to work with Steamworks.NET
// This file is released into the public domain.
// Where that dedication is not recognized you are granted a perpetual,
// irrevocable license to copy and modify this file as you see fit.
//
// Version: 1.0.12
#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#endif
using UnityEngine;
#if !DISABLESTEAMWORKS
using System.Collections;
using Steamworks;
using KCM;
using Riptide.Demos.Steam.PlayerHosted;
#endif
//
// The SteamManager provides a base implementation of Steamworks.NET on which you can build upon.
// It handles the basics of starting up and shutting down the SteamAPI for use.
//
[DisallowMultipleComponent]
public class KCMSteamManager : MonoBehaviour {
#if !DISABLESTEAMWORKS
protected static bool s_EverInitialized = false;
protected static KCMSteamManager s_instance;
public static KCMSteamManager Instance {
get {
if (s_instance == null) {
return new GameObject("KCMSteamManager").AddComponent<KCMSteamManager>();
}
else {
return s_instance;
}
}
}
protected bool m_bInitialized = false;
public static bool Initialized {
get {
return Instance.m_bInitialized;
}
}
protected SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;
[AOT.MonoPInvokeCallback(typeof(SteamAPIWarningMessageHook_t))]
protected static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText) {
Main.helper.Log(pchDebugText.ToString());
}
#if UNITY_2019_3_OR_NEWER
// In case of disabled Domain Reload, reset static members before entering Play Mode.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void InitOnPlayMode()
{
s_EverInitialized = false;
s_instance = null;
}
#endif
protected virtual void Awake() {
// Only one instance of SteamManager at a time!
Main.helper.Log("Steam awake");
if (s_instance != null) {
Destroy(gameObject);
return;
}
s_instance = this;
if(s_EverInitialized) {
// This is almost always an error.
// The most common case where this happens is when SteamManager gets destroyed because of Application.Quit(),
// and then some Steamworks code in some other OnDestroy gets called afterwards, creating a new SteamManager.
// You should never call Steamworks functions in OnDestroy, always prefer OnDisable if possible.
Main.helper.Log("Tried to Initialize the SteamAPI twice in one session!");
return;
}
// We want our SteamManager Instance to persist across scenes.
DontDestroyOnLoad(gameObject);
if (!Packsize.Test()) {
Main.helper.Log("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.");
}
if (!DllCheck.Test()) {
Main.helper.Log("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.");
}
try {
// If Steam is not running or the game wasn't started through Steam, SteamAPI_RestartAppIfNecessary starts the
// Steam client and also launches this game again if the User owns it. This can act as a rudimentary form of DRM.
// Once you get a Steam AppID assigned by Valve, you need to replace AppId_t.Invalid with it and
// remove steam_appid.txt from the game depot. eg: "(AppId_t)480" or "new AppId_t(480)".
// See the Valve documentation for more information: https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
if (SteamAPI.RestartAppIfNecessary((AppId_t)569480)) {
//Application.Quit();
Main.helper.Log("Attempted to restart app");
return;
}
}
catch (System.DllNotFoundException e) { // We catch this exception here, as it will be the first occurrence of it.
Main.helper.Log("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + e);
//Application.Quit();
return;
}
// Initializes the Steamworks API.
// If this returns false then this indicates one of the following conditions:
// [*] The Steam client isn't running. A running Steam client is required to provide implementations of the various Steamworks interfaces.
// [*] The Steam client couldn't determine the App ID of game. If you're running your application from the executable or debugger directly then you must have a [code-inline]steam_appid.txt[/code-inline] in your game directory next to the executable, with your app ID in it and nothing else. Steam will look for this file in the current working directory. If you are running your executable from a different directory you may need to relocate the [code-inline]steam_appid.txt[/code-inline] file.
// [*] Your application is not running under the same OS user context as the Steam client, such as a different user or administration access level.
// [*] Ensure that you own a license for the App ID on the currently active Steam account. Your game must show up in your Steam library.
// [*] Your App ID is not completely set up, i.e. in Release State: Unavailable, or it's missing default packages.
// Valve's documentation for this is located here:
// https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
m_bInitialized = SteamAPI.Init();
if (!m_bInitialized) {
Main.helper.Log("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.");
return;
}
s_EverInitialized = true;
}
// This should only ever get called on first load and after an Assembly reload, You should never Disable the Steamworks Manager yourself.
protected virtual void OnEnable() {
if (s_instance == null) {
s_instance = this;
}
if (!m_bInitialized) {
return;
}
if (m_SteamAPIWarningMessageHook == null) {
// Set up our callback to receive warning messages from Steam.
// You must launch with "-debug_steamapi" in the launch args to receive warnings.
m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook);
SteamClient.SetWarningMessageHook(m_SteamAPIWarningMessageHook);
}
}
// OnApplicationQuit gets called too early to shutdown the SteamAPI.
// Because the SteamManager should be persistent and never disabled or destroyed we can shutdown the SteamAPI here.
// Thus it is not recommended to perform any Steamworks work in other OnDestroy functions as the order of execution can not be garenteed upon Shutdown. Prefer OnDisable().
protected virtual void OnDestroy() {
if (s_instance != this) {
return;
}
s_instance = null;
if (!m_bInitialized) {
return;
}
SteamAPI.Shutdown();
}
protected virtual void Update() {
if (!m_bInitialized) {
return;
}
// Run Steam client callbacks
SteamAPI.RunCallbacks();
}
#else
public static bool Initialized {
get {
return false;
}
}
#endif //!DISABLESTEAMWORKS
}

View File

@@ -0,0 +1,175 @@
using KCM;
using KCM.Enums;
using KCM.Packets.Handlers;
using Steamworks;
using UnityEngine;
namespace Riptide.Demos.Steam.PlayerHosted
{
public class LobbyManager : MonoBehaviour
{
private static LobbyManager _singleton;
internal static LobbyManager Singleton
{
get => _singleton;
private set
{
if (_singleton == null)
_singleton = value;
else if (_singleton != value)
{
Debug.Log($"{nameof(LobbyManager)} instance already exists, destroying object!");
Destroy(value);
}
}
}
protected Callback<LobbyCreated_t> lobbyCreated;
protected Callback<GameLobbyJoinRequested_t> gameLobbyJoinRequested;
protected Callback<LobbyEnter_t> lobbyEnter;
private const string HostAddressKey = "HostAddress";
private CSteamID lobbyId;
private void Awake()
{
Singleton = this;
}
private void Start()
{
if (!KCMSteamManager.Initialized)
{
Main.helper.Log("Steam is not initialized!");
return;
}
lobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreated);
gameLobbyJoinRequested = Callback<GameLobbyJoinRequested_t>.Create(OnGameLobbyJoinRequested);
lobbyEnter = Callback<LobbyEnter_t>.Create(OnLobbyEnter);
}
public static bool loadingSave = false;
internal void CreateLobby(bool loadingSave = false)
{
var result = SteamMatchmaking.CreateLobby(ELobbyType.k_ELobbyTypePublic, 25);
LobbyManager.loadingSave = loadingSave;
}
private void OnLobbyCreated(LobbyCreated_t callback)
{
if (callback.m_eResult != EResult.k_EResultOK)
{
//UIManager.Singleton.LobbyCreationFailed();
Main.helper.Log("Create lobby failed");
return;
}
lobbyId = new CSteamID(callback.m_ulSteamIDLobby);
//UIManager.Singleton.LobbyCreationSucceeded(callback.m_ulSteamIDLobby);
//NetworkManager.Singleton.Server.Start(0, 5, NetworkManager.PlayerHostedDemoMessageHandlerGroupId);
KCServer.StartServer();
Main.TransitionTo(MenuState.ServerLobby);
try
{
Main.helper.Log("About to call connect");
KCClient.Connect("127.0.0.1");
World.inst.Generate();
ServerLobbyScript.WorldSeed.text = World.inst.GetTextSeed();
LobbyHandler.ClearPlayerList();
/*Cam.inst.desiredDist = 80f;
Cam.inst.desiredPhi = 45f;
CloudSystem.inst.threshold1 = 0.6f;
CloudSystem.inst.threshold2 = 0.8f;
CloudSystem.inst.BaseFreq = 4.5f;
Weather.inst.SetSeason(Weather.Season.Summer);
Main.TransitionTo(MenuState.NameAndBanner);*/
ServerBrowser.registerServer = true;
}
catch (System.Exception ex)
{
Main.helper.Log("----------------------- Main exception -----------------------");
Main.helper.Log(ex.ToString());
Main.helper.Log("----------------------- Main message -----------------------");
Main.helper.Log(ex.Message);
Main.helper.Log("----------------------- Main stacktrace -----------------------");
Main.helper.Log(ex.StackTrace);
if (ex.InnerException != null)
{
Main.helper.Log("----------------------- Inner exception -----------------------");
Main.helper.Log(ex.InnerException.ToString());
Main.helper.Log("----------------------- Inner message -----------------------");
Main.helper.Log(ex.InnerException.Message);
Main.helper.Log("----------------------- Inner stacktrace -----------------------");
Main.helper.Log(ex.InnerException.StackTrace);
}
}
//NetworkManager.Singleton.Client.Connect("127.0.0.1", messageHandlerGroupId: NetworkManager.PlayerHostedDemoMessageHandlerGroupId);
}
internal void JoinLobby(ulong lobbyId)
{
SteamMatchmaking.JoinLobby(new CSteamID(lobbyId));
}
private void OnGameLobbyJoinRequested(GameLobbyJoinRequested_t callback)
{
SteamMatchmaking.JoinLobby(callback.m_steamIDLobby);
}
private void OnLobbyEnter(LobbyEnter_t callback)
{
if (KCServer.IsRunning)
return;
lobbyId = new CSteamID(callback.m_ulSteamIDLobby);
CSteamID hostId = SteamMatchmaking.GetLobbyOwner(lobbyId);
KCClient.Connect(hostId.ToString());
//UIManager.Singleton.LobbyEntered();
}
public void LeaveLobby()
{
//NetworkManager.Singleton.StopServer();
//NetworkManager.Singleton.DisconnectClient();
SteamMatchmaking.LeaveLobby(lobbyId);
if (KCClient.client.IsConnected)
KCClient.client.Disconnect();
Main.helper.Log("clear players");
Main.kCPlayers.Clear();
LobbyHandler.ClearPlayerList();
LobbyHandler.ClearChatEntries();
Main.helper.Log("end clear players");
if (KCServer.IsRunning)
KCServer.server.Stop();
Main.TransitionTo(MenuState.ServerBrowser);
ServerBrowser.registerServer = false;
}
}
}

View File

@@ -0,0 +1,204 @@
// This file is provided under The MIT License as part of RiptideSteamTransport.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/tom-weiland/RiptideSteamTransport/blob/main/LICENSE.md
using Steamworks;
using System;
using System.Threading.Tasks;
using UnityEngine;
namespace Riptide.Transports.Steam
{
public class SteamClient : SteamPeer, IClient
{
public event EventHandler Connected;
public event EventHandler ConnectionFailed;
public event EventHandler<DataReceivedEventArgs> DataReceived;
public event EventHandler<DisconnectedEventArgs> Disconnected;
private const string LocalHostName = "localhost";
private const string LocalHostIP = "127.0.0.1";
private SteamConnection steamConnection;
private SteamServer localServer;
private Callback<SteamNetConnectionStatusChangedCallback_t> connectionStatusChanged;
public SteamClient(SteamServer localServer = null)
{
this.localServer = localServer;
}
public void ChangeLocalServer(SteamServer newLocalServer)
{
localServer = newLocalServer;
}
public bool Connect(string hostAddress, out Connection connection, out string connectError)
{
connection = null;
try
{
//SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
SteamNetworkingUtils.InitRelayNetworkAccess();
}
catch (Exception ex)
{
connectError = $"Couldn't connect: {ex}";
return false;
}
connectError = $"Invalid host address '{hostAddress}'! Expected '{LocalHostIP}' or '{LocalHostName}' for local connections, or a valid Steam ID.";
if (hostAddress == LocalHostIP || hostAddress == LocalHostName)
{
if (localServer == null)
{
connectError = $"No locally running server was specified to connect to! Either pass a {nameof(SteamServer)} instance to your {nameof(SteamClient)}'s constructor or call its {nameof(SteamClient.ChangeLocalServer)} method before attempting to connect locally.";
connection = null;
return false;
}
connection = steamConnection = ConnectLocal();
return true;
}
else if (ulong.TryParse(hostAddress, out ulong hostId))
{
connection = steamConnection = TryConnect(new CSteamID(hostId));
return connection != null;
}
return false;
}
private SteamConnection ConnectLocal()
{
Debug.Log($"{LogName}: Connecting to locally running server...");
connectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
CSteamID playerSteamId = SteamUser.GetSteamID();
SteamNetworkingIdentity clientIdentity = new SteamNetworkingIdentity();
clientIdentity.SetSteamID(playerSteamId);
SteamNetworkingIdentity serverIdentity = new SteamNetworkingIdentity();
serverIdentity.SetSteamID(playerSteamId);
SteamNetworkingSockets.CreateSocketPair(out HSteamNetConnection connectionToClient, out HSteamNetConnection connectionToServer, false, ref clientIdentity, ref serverIdentity);
localServer.Add(new SteamConnection(playerSteamId, connectionToClient, this));
OnConnected();
return new SteamConnection(playerSteamId, connectionToServer, this);
}
private SteamConnection TryConnect(CSteamID hostId)
{
try
{
connectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
SteamNetworkingIdentity serverIdentity = new SteamNetworkingIdentity();
serverIdentity.SetSteamID(hostId);
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
HSteamNetConnection connectionToServer = SteamNetworkingSockets.ConnectP2P(ref serverIdentity, 0, options.Length, options);
ConnectTimeout();
return new SteamConnection(hostId, connectionToServer, this);
}
catch (Exception ex)
{
Debug.LogException(ex);
OnConnectionFailed();
return null;
}
}
private async void ConnectTimeout() // TODO: confirm if this is needed, Riptide *should* take care of timing out the connection
{
Task timeOutTask = Task.Delay(6000); // TODO: use Riptide Client's TimeoutTime
await Task.WhenAny(timeOutTask);
if (!steamConnection.IsConnected)
OnConnectionFailed();
}
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback)
{
if (!callback.m_hConn.Equals(steamConnection.SteamNetConnection))
{
// When connecting via local loopback connection to a locally running SteamServer (aka
// this player is also the host), other external clients that attempt to connect seem
// to trigger ConnectionStatusChanged callbacks for the locally connected client. Not
// 100% sure why this is the case, but returning out of the callback here when the
// connection doesn't match that between local client & server avoids the problem.
return;
}
switch (callback.m_info.m_eState)
{
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
OnConnected();
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer:
SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Closed by peer", false);
OnDisconnected(DisconnectReason.Disconnected);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Problem detected", false);
OnDisconnected(DisconnectReason.TransportError);
break;
default:
Debug.Log($"{LogName}: Connection state changed - {callback.m_info.m_eState} | {callback.m_info.m_szEndDebug}");
break;
}
}
public void Poll()
{
if (steamConnection != null)
Receive(steamConnection);
}
// TODO: disable nagle so this isn't needed
//public void Flush()
//{
// foreach (SteamConnection connection in connections.Values)
// SteamNetworkingSockets.FlushMessagesOnConnection(connection.SteamNetConnection);
//}
public void Disconnect()
{
if (connectionStatusChanged != null)
{
connectionStatusChanged.Dispose();
connectionStatusChanged = null;
}
SteamNetworkingSockets.CloseConnection(steamConnection.SteamNetConnection, 0, "Disconnected", false);
steamConnection = null;
}
protected virtual void OnConnected()
{
Connected?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnConnectionFailed()
{
ConnectionFailed?.Invoke(this, EventArgs.Empty);
}
protected override void OnDataReceived(byte[] dataBuffer, int amount, SteamConnection fromConnection)
{
DataReceived?.Invoke(this, new DataReceivedEventArgs(dataBuffer, amount, fromConnection));
}
protected virtual void OnDisconnected(DisconnectReason reason)
{
Disconnected?.Invoke(this, new DisconnectedEventArgs(steamConnection, reason));
}
}
}

View File

@@ -0,0 +1,72 @@
// This file is provided under The MIT License as part of RiptideSteamTransport.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/tom-weiland/RiptideSteamTransport/blob/main/LICENSE.md
using Steamworks;
using System;
using System.Collections.Generic;
namespace Riptide.Transports.Steam
{
public class SteamConnection : Connection, IEquatable<SteamConnection>
{
public readonly CSteamID SteamId;
public readonly HSteamNetConnection SteamNetConnection;
internal bool DidReceiveConnect;
private readonly SteamPeer peer;
internal SteamConnection(CSteamID steamId, HSteamNetConnection steamNetConnection, SteamPeer peer)
{
SteamId = steamId;
SteamNetConnection = steamNetConnection;
this.peer = peer;
}
protected internal override void Send(byte[] dataBuffer, int amount)
{
peer.Send(dataBuffer, amount, SteamNetConnection);
}
/// <inheritdoc/>
public override string ToString() => SteamNetConnection.ToString();
/// <inheritdoc/>
public override bool Equals(object obj) => Equals(obj as SteamConnection);
/// <inheritdoc/>
public bool Equals(SteamConnection other)
{
if (other is null)
return false;
if (ReferenceEquals(this, other))
return true;
return SteamNetConnection.Equals(other.SteamNetConnection);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return -721414014 + EqualityComparer<HSteamNetConnection>.Default.GetHashCode(SteamNetConnection);
}
public static bool operator ==(SteamConnection left, SteamConnection right)
{
if (left is null)
{
if (right is null)
return true;
return false; // Only the left side is null
}
// Equals handles case of null on right side
return left.Equals(right);
}
public static bool operator !=(SteamConnection left, SteamConnection right) => !(left == right);
}
}

View File

@@ -0,0 +1,69 @@
// This file is provided under The MIT License as part of RiptideSteamTransport.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/tom-weiland/RiptideSteamTransport/blob/main/LICENSE.md
using Steamworks;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Riptide.Transports.Steam
{
public abstract class SteamPeer
{
/// <summary>The name to use when logging messages via <see cref="Utils.RiptideLogger"/>.</summary>
public const string LogName = "STEAM";
protected const int MaxMessages = 256;
private readonly byte[] receiveBuffer;
protected SteamPeer()
{
receiveBuffer = new byte[Message.MaxSize + sizeof(ushort)];
}
protected void Receive(SteamConnection fromConnection)
{
IntPtr[] ptrs = new IntPtr[MaxMessages]; // TODO: remove allocation?
// TODO: consider using poll groups -> https://partner.steamgames.com/doc/api/ISteamNetworkingSockets#functions_poll_groups
int messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(fromConnection.SteamNetConnection, ptrs, MaxMessages);
if (messageCount > 0)
{
for (int i = 0; i < messageCount; i++)
{
SteamNetworkingMessage_t data = Marshal.PtrToStructure<SteamNetworkingMessage_t>(ptrs[i]);
if (data.m_cbSize > 0)
{
int byteCount = data.m_cbSize;
if (data.m_cbSize > receiveBuffer.Length)
{
Debug.LogWarning($"{LogName}: Can't fully handle {data.m_cbSize} bytes because it exceeds the maximum of {receiveBuffer.Length}. Data will be incomplete!");
byteCount = receiveBuffer.Length;
}
Marshal.Copy(data.m_pData, receiveBuffer, 0, data.m_cbSize);
OnDataReceived(receiveBuffer, byteCount, fromConnection);
}
}
}
}
internal void Send(byte[] dataBuffer, int numBytes, HSteamNetConnection toConnection)
{
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
IntPtr pDataBuffer = handle.AddrOfPinnedObject();
EResult result = SteamNetworkingSockets.SendMessageToConnection(toConnection, pDataBuffer, (uint)numBytes, Constants.k_nSteamNetworkingSend_Unreliable, out long _);
if (result != EResult.k_EResultOK)
Debug.LogWarning($"{LogName}: Failed to send {numBytes} bytes - {result}");
handle.Free();
}
protected abstract void OnDataReceived(byte[] dataBuffer, int amount, SteamConnection fromConnection);
}
}

View File

@@ -0,0 +1,160 @@
// This file is provided under The MIT License as part of RiptideSteamTransport.
// Copyright (c) Tom Weiland
// For additional information please see the included LICENSE.md file or view it on GitHub:
// https://github.com/tom-weiland/RiptideSteamTransport/blob/main/LICENSE.md
using Steamworks;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Riptide.Transports.Steam
{
public class SteamServer : SteamPeer, IServer
{
public event EventHandler<ConnectedEventArgs> Connected;
public event EventHandler<DataReceivedEventArgs> DataReceived;
public event EventHandler<DisconnectedEventArgs> Disconnected;
public ushort Port { get; private set; }
private Dictionary<CSteamID, SteamConnection> connections;
private HSteamListenSocket listenSocket;
private Callback<SteamNetConnectionStatusChangedCallback_t> connectionStatusChanged;
public void Start(ushort port)
{
Port = port;
connections = new Dictionary<CSteamID, SteamConnection>();
connectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
try
{
#if UNITY_SERVER
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
#else
SteamNetworkingUtils.InitRelayNetworkAccess();
#endif
}
catch (Exception ex)
{
Debug.LogException(ex);
}
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(port, options.Length, options);
}
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback)
{
CSteamID clientSteamId = callback.m_info.m_identityRemote.GetSteamID();
switch (callback.m_info.m_eState)
{
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting:
Accept(callback.m_hConn);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
Add(new SteamConnection(clientSteamId, callback.m_hConn, this));
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer:
SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Closed by peer", false);
OnDisconnected(clientSteamId, DisconnectReason.Disconnected);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
SteamNetworkingSockets.CloseConnection(callback.m_hConn, 0, "Problem detected", false);
OnDisconnected(clientSteamId, DisconnectReason.TransportError);
break;
default:
Debug.Log($"{LogName}: {clientSteamId}'s connection state changed - {callback.m_info.m_eState}");
break;
}
}
internal void Add(SteamConnection connection)
{
if (!connections.ContainsKey(connection.SteamId))
{
connections.Add(connection.SteamId, connection);
OnConnected(connection);
}
else
Debug.Log($"{LogName}: Connection from {connection.SteamId} could not be accepted: Already connected");
}
private void Accept(HSteamNetConnection connection)
{
EResult result = SteamNetworkingSockets.AcceptConnection(connection);
if (result != EResult.k_EResultOK)
Debug.LogWarning($"{LogName}: Connection could not be accepted: {result}");
}
public void Close(Connection connection)
{
if (connection is SteamConnection steamConnection)
{
SteamNetworkingSockets.CloseConnection(steamConnection.SteamNetConnection, 0, "Disconnected by server", false);
connections.Remove(steamConnection.SteamId);
}
}
public void Poll()
{
foreach (SteamConnection connection in connections.Values)
Receive(connection);
}
// TODO: disable nagle so this isn't needed
//public void Flush()
//{
// foreach (SteamConnection connection in connections.Values)
// SteamNetworkingSockets.FlushMessagesOnConnection(connection.SteamNetConnection);
//}
public void Shutdown()
{
if (connectionStatusChanged != null)
{
connectionStatusChanged.Dispose();
connectionStatusChanged = null;
}
foreach (SteamConnection connection in connections.Values)
SteamNetworkingSockets.CloseConnection(connection.SteamNetConnection, 0, "Server stopped", false);
connections.Clear();
SteamNetworkingSockets.CloseListenSocket(listenSocket);
}
protected internal virtual void OnConnected(Connection connection)
{
Connected?.Invoke(this, new ConnectedEventArgs(connection));
}
protected override void OnDataReceived(byte[] dataBuffer, int amount, SteamConnection fromConnection)
{
if ((MessageHeader)dataBuffer[0] == MessageHeader.Connect)
{
if (fromConnection.DidReceiveConnect)
return;
fromConnection.DidReceiveConnect = true;
}
DataReceived?.Invoke(this, new DataReceivedEventArgs(dataBuffer, amount, fromConnection));
}
protected virtual void OnDisconnected(CSteamID steamId, DisconnectReason reason)
{
if (connections.TryGetValue(steamId, out SteamConnection connection))
{
Disconnected?.Invoke(this, new DisconnectedEventArgs(connection, reason));
connections.Remove(steamId);
}
}
}
}