-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathSteamClient.cs
203 lines (169 loc) · 8.06 KB
/
SteamClient.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// This file is a 1:1 conversion from Tom Weilands Riptide Transport https://github.com/tom-weiland/RiptideSteamTransport/
using Steamworks;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Godot;
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
{
if (OS.HasFeature("dedicated_server"))
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
else
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()
{
GD.Print($"{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)
{
GD.PrintErr(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:
GD.Print($"{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));
}
}
}