2 using System.Collections.Immutable;
4 using System.Threading.Tasks;
5 using Microsoft.Xna.Framework;
9 internal abstract class ClientPeer<TEndpoint> : ClientPeer where TEndpoint :
Endpoint
11 public new TEndpoint ServerEndpoint => (base.ServerEndpoint as TEndpoint)!;
12 protected ClientPeer(TEndpoint serverEndpoint, ImmutableArray<Endpoint> allServerEndpoints, Callbacks callbacks, Option<int> ownerKey)
13 : base(serverEndpoint, allServerEndpoints, callbacks, ownerKey) { }
16 internal abstract class ClientPeer
18 public ImmutableArray<ServerContentPackage> ServerContentPackages {
get;
set; } =
19 ImmutableArray<ServerContentPackage>.Empty;
21 public bool AllowModDownloads {
get;
private set; } =
true;
23 public readonly record
struct Callbacks(
24 Callbacks.MessageCallback OnMessageReceived,
25 Callbacks.DisconnectCallback OnDisconnect,
26 Callbacks.InitializationCompleteCallback OnInitializationComplete)
28 public delegate
void MessageCallback(IReadMessage message);
29 public delegate
void DisconnectCallback(PeerDisconnectPacket disconnectPacket);
30 public delegate
void InitializationCompleteCallback();
33 protected readonly Callbacks callbacks;
35 public readonly
Endpoint ServerEndpoint;
36 public readonly ImmutableArray<Endpoint> AllServerEndpoints;
37 public NetworkConnection? ServerConnection {
get;
protected set; }
39 protected bool IsOwner => ownerKey.IsSome();
40 protected readonly Option<int> ownerKey;
42 public bool IsActive => isActive;
44 protected bool isActive;
46 protected ClientPeer(Endpoint serverEndpoint, ImmutableArray<Endpoint> allServerEndpoints, Callbacks callbacks, Option<int> ownerKey)
48 ServerEndpoint = serverEndpoint;
49 AllServerEndpoints = allServerEndpoints;
50 this.callbacks = callbacks;
51 this.ownerKey = ownerKey;
54 public abstract void Start();
55 public abstract void Close(PeerDisconnectPacket peerDisconnectPacket);
56 public abstract void Update(
float deltaTime);
57 public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod,
bool compressPastThreshold =
true);
58 public abstract void SendPassword(
string password);
60 protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body);
62 protected ConnectionInitialization initializationStep;
63 public bool ContentPackageOrderReceived {
get;
set; }
64 protected int passwordSalt;
65 protected Option<AuthenticationTicket> authTicket;
66 private GUIMessageBox? passwordMsgBox;
68 public bool WaitingForPassword
69 => isActive && initializationStep == ConnectionInitialization.Password
70 && passwordMsgBox !=
null
71 && GUIMessageBox.MessageBoxes.Contains(passwordMsgBox);
79 protected abstract Task<Option<AccountId>> GetAccountId();
81 protected void OnInitializationComplete()
83 passwordMsgBox?.Close();
84 if (initializationStep == ConnectionInitialization.Success) {
return; }
86 callbacks.OnInitializationComplete.Invoke();
87 initializationStep = ConnectionInitialization.Success;
90 protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
92 if (inc.InitializationStep != ConnectionInitialization.Password)
94 passwordMsgBox?.Close();
97 switch (inc.InitializationStep)
99 case ConnectionInitialization.AuthInfoAndVersion:
101 if (initializationStep != ConnectionInitialization.AuthInfoAndVersion) {
return; }
103 TaskPool.Add($
"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
105 if (GameMain.Client?.ClientPeer is
null) { return; }
107 if (!t.TryGetResult(out Option<AccountId> accountId))
109 Close(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
112 var headers =
new PeerPacketHeaders
114 DeliveryMethod = DeliveryMethod.Reliable,
115 PacketHeader = PacketHeader.IsConnectionInitializationStep,
116 Initialization = ConnectionInitialization.AuthInfoAndVersion
119 var body =
new ClientAuthTicketAndVersionPacket
121 Name = GameMain.Client.Name,
123 AccountId = accountId,
124 AuthTicket = authTicket,
125 GameVersion = GameMain.Version.ToString(),
126 Language = GameSettings.CurrentConfig.Language.Value
129 SendMsgInternal(headers, body);
133 case ConnectionInitialization.ContentPackageOrder:
135 if (initializationStep
136 is ConnectionInitialization.AuthInfoAndVersion
137 or ConnectionInitialization.Password)
139 initializationStep = ConnectionInitialization.ContentPackageOrder;
142 if (initializationStep != ConnectionInitialization.ContentPackageOrder) {
return; }
144 PeerPacketHeaders headers =
new PeerPacketHeaders
146 DeliveryMethod = DeliveryMethod.Reliable,
147 PacketHeader = PacketHeader.IsConnectionInitializationStep,
148 Initialization = ConnectionInitialization.ContentPackageOrder
151 var orderPacket = INetSerializableStruct.Read<ServerPeerContentPackageOrderPacket>(inc.Message);
153 if (!ContentPackageOrderReceived)
155 ServerContentPackages = orderPacket.ContentPackages;
156 AllowModDownloads = orderPacket.AllowModDownloads;
157 if (ServerContentPackages.Length == 0)
159 string errorMsg =
"Error in ContentPackageOrder message: list of content packages enabled on the server was empty.";
160 GameAnalyticsManager.AddErrorEventOnce(
"ClientPeer.ReadConnectionInitializationStep:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
161 DebugConsole.ThrowError(errorMsg);
163 ContentPackageOrderReceived =
true;
165 SendMsgInternal(headers,
null);
169 case ConnectionInitialization.Password:
170 if (initializationStep == ConnectionInitialization.AuthInfoAndVersion)
172 initializationStep = ConnectionInitialization.Password;
175 if (initializationStep != ConnectionInitialization.Password) {
return; }
177 var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
179 if (WaitingForPassword) {
return; }
181 passwordPacket.Salt.TryUnwrap(out passwordSalt);
182 passwordPacket.RetriesLeft.TryUnwrap(out var retries);
184 LocalizedString pwMsg = TextManager.Get(
"PasswordRequired");
186 passwordMsgBox?.Close();
187 passwordMsgBox =
new GUIMessageBox(pwMsg,
"",
new LocalizedString[] { TextManager.Get(
"OK"), TextManager.Get(
"Cancel") },
188 relativeSize:
new Vector2(0.25f, 0.1f), minSize:
new Point(400, GUI.IntScale(170)));
189 var passwordHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.5f), passwordMsgBox.Content.RectTransform), childAnchor:
Anchor.TopCenter);
190 var passwordBox =
new GUITextBox(
new RectTransform(
new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) })
197 var incorrectPasswordText =
new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.0f), passwordHolder.RectTransform), TextManager.Get(
"incorrectpassword"), GUIStyle.Red, GUIStyle.Font, textAlignment: Alignment.Center);
198 incorrectPasswordText.RectTransform.MinSize =
new Point(0, (
int)incorrectPasswordText.TextSize.Y);
199 passwordHolder.Recalculate();
202 passwordMsgBox.Content.Recalculate();
203 passwordMsgBox.Content.RectTransform.MinSize =
new Point(0, passwordMsgBox.Content.RectTransform.Children.Sum(c => c.Rect.Height));
204 passwordMsgBox.Content.Parent.RectTransform.MinSize =
new Point(0, (
int)(passwordMsgBox.Content.RectTransform.MinSize.Y / passwordMsgBox.Content.RectTransform.RelativeSize.Y));
206 var okButton = passwordMsgBox.Buttons[0];
207 okButton.OnClicked += (_, __) =>
209 SendPassword(passwordBox.Text);
212 okButton.OnClicked += passwordMsgBox.Close;
214 var cancelButton = passwordMsgBox.Buttons[1];
215 cancelButton.OnClicked = (_, __) =>
217 Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
218 passwordMsgBox?.Close(); passwordMsgBox =
null;
223 passwordBox.OnEnterPressed += (_, __) =>
225 okButton.OnClicked.Invoke(okButton, okButton.UserData);
229 passwordBox.Select();
236 public abstract void ForceTimeOut();
238 public abstract void DebugSendRawMessage(IWriteMessage msg);
ConnectionInitialization InitializationStep