74086415fc
commit e08c5e722fdff38d408428d138919bb8ea90321b Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Feb 5 11:13:25 2019 +0200 Changed default radio chat keybind to T. OemTilde works as an apostrophe on some keyboard layouts, making it difficult to type in chat messages without deselecting the chatbox. Closes #1044 commit e0ca9ffebe06b3f711b28a3ccfd486d84c47f9b0 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Tue Feb 5 11:03:14 2019 +0200 Fixed content package compatibility check. Closes #1059 commit f54bc180e8aef383cf9c893cd44c103840c066ac Author: itchyOwl <lauri.harkanen@gmail.com> Date: Tue Feb 5 10:48:03 2019 +0200 Remove the spacing from the crew selection listbox and disable focusing from the background. An alternative fix for #1039. commit d037815054ecc8269b0af3297700d2b98e1e5bc5 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Tue Feb 5 10:40:52 2019 +0200 Enforce vsync during the splash screen. Should fix the crashes due to insufficient memory mentioned in #1060. Does not fix the issue on releasing the used video resources. commit 90f1d8fe168356ba3c797c0c58fadea2aff57458 Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 23:05:54 2019 +0200 small updates to subs commit 56dbdf6ea6e8e14035f943eb232eed7aa250f8f6 Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 23:05:26 2019 +0200 adjusted default deterioration values on prefabs commit eaa4818cb63d52e1af519c8ca5c4f01f20143cb2 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 21:59:03 2019 +0200 Fixed servers occasionally starting the round multiple times when automatically starting the game via autorestart or clients being ready. Happened because "initiatedStartGame" (which prevents starting the round again) was not set until the InitiateStartGame coroutine is run for the first time, causing the automatic restart logic to run one extra time. Closes #1056 commit afd83b6ef40333a95e5eeec400d5f981cbdb1889 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 21:36:35 2019 +0200 Fixed some typos in affliction & mission descriptions. Closes #1057 commit feb97628582a9a22f0660a8ab2798f8e03fe8fc9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 18:29:40 2019 +0200 Fixes to Level.TryGetInterestingPosition. Should fix monsters spawning very close to the sub in some levels (see #1054). - Ignore outposts when determining which positions are far enough from the sub. - If no position far enough from the sub is found, use the furthest one instead of choosing randomly. commit 62e5d7b229f334a5cf15cedf22e6795f18d637b9 Merge: 6ffc90da6 73f3dd060 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 18:03:44 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 6ffc90da69d9a5db146435054fdbb2cb18d855be Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 18:03:26 2019 +0200 Allow to focus on the crew selection listbox to enable scrolling when the mouse cursor is not over a content element, but is inside the listbox. Another option would be to remove the spacing from the listbox. Fixes #1039. commit 73f3dd060ef208199941cb74a72dcda6a7c15f41 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 18:00:57 2019 +0200 Fixed compiler error in GameServer.ClientWriteIngame commit 131263490c7093dd526a84757f4edd096a213a74 Merge: 637668488 9753e46a5 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 17:55:39 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 637668488b27954afa0a8485eeaa3b23cf9461c5 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 17:55:01 2019 +0200 Changed character culling logic a bit. The characters need to be 22,000 units away from the players to be disabled, and won't be re-enabled until they're within 20,000 units. Using 20,000 as the threshold for both caused characters to be constantly toggled on/off when they're around the maximum distance, which caused ContactManager to constantly have to update contacts. commit 9753e46a5694b7b3c2bd0c4c006e2c1856c6eddb Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 17:54:35 2019 +0200 Add a debug command for adding more money in the compaign mode. commit f8a7179d6f8cb7c834b91b942722b1ae05417316 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 17:53:03 2019 +0200 Always disallow zooming when the cursor is over a gui element. commit eefd956c50650be906acbd10662e24507e48cb83 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 17:47:18 2019 +0200 Don't display outpost markers on the sonar (the ending and starting positions of the level are displayed, no need to show an additional marker on the "outpost submarine"). commit c7883bafc634e0ac2603bc96a87433da0c3481df Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 17:21:50 2019 +0200 removed references to medical syringes
1909 lines
81 KiB
C#
1909 lines
81 KiB
C#
using Barotrauma.Steam;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Barotrauma.Networking
|
|
{
|
|
class GameClient : NetworkMember
|
|
{
|
|
private NetClient client;
|
|
|
|
private GUIMessageBox reconnectBox, waitInServerQueueBox;
|
|
|
|
private GUIButton endRoundButton;
|
|
private GUITickBox endVoteTickBox;
|
|
|
|
private ClientPermissions permissions = ClientPermissions.None;
|
|
private List<string> permittedConsoleCommands = new List<string>();
|
|
|
|
private bool connected;
|
|
|
|
private byte myID;
|
|
|
|
private List<Client> otherClients;
|
|
|
|
private string serverIP;
|
|
|
|
private bool needAuth;
|
|
private bool requiresPw;
|
|
private int nonce;
|
|
private string saltedPw;
|
|
private Facepunch.Steamworks.Auth.Ticket steamAuthTicket;
|
|
|
|
private UInt16 lastSentChatMsgID = 0; //last message this client has successfully sent
|
|
private UInt16 lastQueueChatMsgID = 0; //last message added to the queue
|
|
private List<ChatMessage> chatMsgQueue = new List<ChatMessage>();
|
|
|
|
public UInt16 LastSentEntityEventID;
|
|
|
|
private ClientEntityEventManager entityEventManager;
|
|
|
|
private FileReceiver fileReceiver;
|
|
|
|
//has the client been given a character to control this round
|
|
public bool HasSpawned;
|
|
|
|
public byte ID
|
|
{
|
|
get { return myID; }
|
|
}
|
|
|
|
public override List<Client> ConnectedClients
|
|
{
|
|
get
|
|
{
|
|
return otherClients;
|
|
}
|
|
}
|
|
|
|
public FileReceiver FileReceiver
|
|
{
|
|
get { return fileReceiver; }
|
|
}
|
|
|
|
public bool MidRoundSyncing
|
|
{
|
|
get { return entityEventManager.MidRoundSyncing; }
|
|
}
|
|
|
|
public bool AllowDisguises
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public GameClient(string newName)
|
|
{
|
|
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform),
|
|
isHorizontal: true, childAnchor: Anchor.CenterRight)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
endRoundButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
|
|
TextManager.Get("EndRound"))
|
|
{
|
|
OnClicked = (btn, userdata) =>
|
|
{
|
|
if (!permissions.HasFlag(ClientPermissions.EndRound)) return false;
|
|
RequestRoundEnd();
|
|
return true;
|
|
},
|
|
Visible = false
|
|
};
|
|
|
|
endVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
|
|
TextManager.Get("EndRound"))
|
|
{
|
|
OnSelected = ToggleEndRoundVote,
|
|
Visible = false
|
|
};
|
|
|
|
showLogButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
|
|
TextManager.Get("ServerLog"))
|
|
{
|
|
OnClicked = (GUIButton button, object userData) =>
|
|
{
|
|
if (ServerLog.LogFrame == null)
|
|
{
|
|
ServerLog.CreateLogFrame();
|
|
}
|
|
else
|
|
{
|
|
ServerLog.LogFrame = null;
|
|
GUI.KeyboardDispatcher.Subscriber = null;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
GameMain.DebugDraw = false;
|
|
Hull.EditFire = false;
|
|
Hull.EditWater = false;
|
|
|
|
newName = newName.Replace(":", "").Replace(";", "");
|
|
name = newName;
|
|
|
|
entityEventManager = new ClientEntityEventManager(this);
|
|
|
|
fileReceiver = new FileReceiver();
|
|
fileReceiver.OnFinished += OnFileReceived;
|
|
fileReceiver.OnTransferFailed += OnTransferFailed;
|
|
|
|
characterInfo = new CharacterInfo(Character.HumanConfigFile, name, Gender.None, null)
|
|
{
|
|
Job = null
|
|
};
|
|
|
|
otherClients = new List<Client>();
|
|
|
|
ServerLog = new ServerLog("");
|
|
|
|
ChatMessage.LastID = 0;
|
|
GameMain.NetLobbyScreen = new NetLobbyScreen();
|
|
}
|
|
|
|
public void ConnectToServer(string hostIP)
|
|
{
|
|
string[] address = hostIP.Split(':');
|
|
if (address.Length == 1)
|
|
{
|
|
serverIP = hostIP;
|
|
Port = NetConfig.DefaultPort;
|
|
}
|
|
else
|
|
{
|
|
serverIP = string.Join(":", address.Take(address.Length - 1));
|
|
if (!int.TryParse(address[address.Length - 1], out int port))
|
|
{
|
|
DebugConsole.ThrowError("Invalid port: " + address[address.Length - 1] + "!");
|
|
Port = NetConfig.DefaultPort;
|
|
}
|
|
else
|
|
{
|
|
Port = port;
|
|
}
|
|
}
|
|
|
|
myCharacter = Character.Controlled;
|
|
ChatMessage.LastID = 0;
|
|
|
|
// Create new instance of configs. Parameter is "application Id". It has to be same on client and server.
|
|
NetPeerConfiguration = new NetPeerConfiguration("barotrauma");
|
|
|
|
NetPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt
|
|
| NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error);
|
|
|
|
client = new NetClient(NetPeerConfiguration);
|
|
NetPeer = client;
|
|
client.Start();
|
|
|
|
System.Net.IPEndPoint IPEndPoint = null;
|
|
try
|
|
{
|
|
IPEndPoint = new System.Net.IPEndPoint(NetUtility.Resolve(serverIP), Port);
|
|
}
|
|
catch
|
|
{
|
|
new GUIMessageBox(TextManager.Get("CouldNotConnectToServer"),
|
|
TextManager.Get("InvalidIPAddress").Replace("[serverip]", serverIP).Replace("[port]", Port.ToString()));
|
|
return;
|
|
}
|
|
|
|
NetOutgoingMessage outmsg = client.CreateMessage();
|
|
WriteAuthRequest(outmsg);
|
|
|
|
// Connect client, to ip previously requested from user
|
|
try
|
|
{
|
|
client.Connect(IPEndPoint, outmsg);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DebugConsole.ThrowError("Couldn't connect to " + hostIP + ". Error message: " + e.Message);
|
|
Disconnect();
|
|
|
|
GameMain.ServerListScreen.Select();
|
|
return;
|
|
}
|
|
|
|
updateInterval = new TimeSpan(0, 0, 0, 0, 150);
|
|
|
|
CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo");
|
|
}
|
|
|
|
private bool RetryConnection(GUIButton button, object obj)
|
|
{
|
|
if (client != null) client.Shutdown("Disconnecting");
|
|
ConnectToServer(serverIP);
|
|
return true;
|
|
}
|
|
|
|
private bool ReturnToServerList(GUIButton button, object obj)
|
|
{
|
|
Disconnect();
|
|
|
|
Submarine.Unload();
|
|
GameMain.NetworkMember = null;
|
|
GameMain.GameSession = null;
|
|
GameMain.ServerListScreen.Select();
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool connectCancelled;
|
|
private bool CancelConnect(GUIButton button, object obj)
|
|
{
|
|
connectCancelled = true;
|
|
return true;
|
|
}
|
|
|
|
// Before main looping starts, we loop here and wait for approval message
|
|
private IEnumerable<object> WaitForStartingInfo()
|
|
{
|
|
requiresPw = false;
|
|
needAuth = true;
|
|
saltedPw = "";
|
|
|
|
connectCancelled = false;
|
|
// When this is set to true, we are approved and ready to go
|
|
bool CanStart = false;
|
|
|
|
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20);
|
|
DateTime reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200);
|
|
|
|
// Loop until we are approved
|
|
//TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly)
|
|
string connectingText = TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP);
|
|
while (!CanStart && !connectCancelled)
|
|
{
|
|
if (reconnectBox == null)
|
|
{
|
|
reconnectBox = new GUIMessageBox(TextManager.Get("Connecting"), connectingText, new string[] { TextManager.Get("Cancel") });
|
|
|
|
reconnectBox.Buttons[0].OnClicked += CancelConnect;
|
|
reconnectBox.Buttons[0].OnClicked += reconnectBox.Close;
|
|
}
|
|
|
|
reconnectBox.Text.Text = connectingText + new string('.', ((int)Timing.TotalTime % 3 + 1));
|
|
|
|
if (DateTime.Now > reqAuthTime)
|
|
{
|
|
if (needAuth)
|
|
{
|
|
//request auth again
|
|
NetOutgoingMessage reqAuthMsg = client.CreateMessage();
|
|
WriteAuthRequest(reqAuthMsg);
|
|
client.SendMessage(reqAuthMsg, NetDeliveryMethod.Unreliable);
|
|
}
|
|
else
|
|
{
|
|
//request init again
|
|
NetOutgoingMessage outmsg = client.CreateMessage();
|
|
outmsg.Write((byte)ClientPacketHeader.REQUEST_INIT);
|
|
if (requiresPw)
|
|
{
|
|
outmsg.Write(saltedPw);
|
|
}
|
|
outmsg.Write(GameMain.Version.ToString());
|
|
|
|
var mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
|
|
outmsg.Write((UInt16)mpContentPackages.Count());
|
|
foreach (ContentPackage contentPackage in mpContentPackages)
|
|
{
|
|
outmsg.Write(contentPackage.Name);
|
|
outmsg.Write(contentPackage.MD5hash.Hash);
|
|
}
|
|
outmsg.Write(name);
|
|
client.SendMessage(outmsg, NetDeliveryMethod.Unreliable);
|
|
|
|
DebugConsole.Log("Sending init request (" + (requiresPw ? "password required" : "no password required") + ")");
|
|
}
|
|
reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
|
}
|
|
|
|
yield return CoroutineStatus.Running;
|
|
|
|
if (DateTime.Now > timeOut) break;
|
|
|
|
NetIncomingMessage inc;
|
|
// If new messages arrived
|
|
if ((inc = client.ReadMessage()) == null) continue;
|
|
|
|
string pwMsg = "Password required";
|
|
|
|
try
|
|
{
|
|
switch (inc.MessageType)
|
|
{
|
|
case NetIncomingMessageType.Data:
|
|
DecompressIncomingMessage(inc);
|
|
|
|
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
|
|
switch (header)
|
|
{
|
|
case ServerPacketHeader.AUTH_RESPONSE:
|
|
DebugConsole.Log("Received auth response (needauth: " + needAuth + ")");
|
|
if (needAuth)
|
|
{
|
|
if (inc.ReadBoolean())
|
|
{
|
|
DebugConsole.Log(" password required.");
|
|
//requires password
|
|
nonce = inc.ReadInt32();
|
|
requiresPw = true;
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.Log(" password not required.");
|
|
requiresPw = false;
|
|
reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200);
|
|
}
|
|
needAuth = false; //got auth!
|
|
}
|
|
break;
|
|
case ServerPacketHeader.AUTH_FAILURE:
|
|
//failed to authenticate, can still use same nonce
|
|
DebugConsole.Log("Received auth failure message");
|
|
pwMsg = inc.ReadString();
|
|
requiresPw = true;
|
|
break;
|
|
case ServerPacketHeader.UPDATE_LOBBY:
|
|
DebugConsole.Log("Recived lobby update");
|
|
//server accepted client
|
|
ReadLobbyUpdate(inc);
|
|
CanStart = true;
|
|
break;
|
|
}
|
|
break;
|
|
case NetIncomingMessageType.StatusChanged:
|
|
NetConnectionStatus connectionStatus = (NetConnectionStatus)inc.ReadByte();
|
|
if (connectionStatus == NetConnectionStatus.Disconnected)
|
|
{
|
|
ReadDisconnectMessage(inc, false);
|
|
connectCancelled = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
catch (Exception e)
|
|
{
|
|
DebugConsole.ThrowError("Error while connecting to the server", e);
|
|
break;
|
|
}
|
|
|
|
if (requiresPw && !CanStart && !connectCancelled)
|
|
{
|
|
if (reconnectBox != null)
|
|
{
|
|
reconnectBox.Close(null, null);
|
|
reconnectBox = null;
|
|
}
|
|
|
|
var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") });
|
|
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 0.1f), msgBox.InnerFrame.RectTransform, Anchor.Center))
|
|
{
|
|
IgnoreLayoutGroups = true,
|
|
UserData = "password"
|
|
};
|
|
|
|
var okButton = msgBox.Buttons[0];
|
|
var cancelButton = msgBox.Buttons[1];
|
|
|
|
while (GUIMessageBox.MessageBoxes.Contains(msgBox))
|
|
{
|
|
while (client.ReadMessage() != null)
|
|
{
|
|
switch (inc.MessageType)
|
|
{
|
|
case NetIncomingMessageType.StatusChanged:
|
|
NetConnectionStatus connectionStatus = (NetConnectionStatus)inc.ReadByte();
|
|
if (connectionStatus == NetConnectionStatus.Disconnected)
|
|
{
|
|
ReadDisconnectMessage(inc, false);
|
|
msgBox.Close(null, null);
|
|
connectCancelled = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (DateTime.Now > reqAuthTime)
|
|
{
|
|
//request auth again to prevent timeout
|
|
NetOutgoingMessage reqAuthMsg = client.CreateMessage();
|
|
WriteAuthRequest(reqAuthMsg);
|
|
client.SendMessage(reqAuthMsg, NetDeliveryMethod.Unreliable);
|
|
reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 3);
|
|
}
|
|
|
|
okButton.Enabled = !string.IsNullOrWhiteSpace(passwordBox.Text);
|
|
|
|
if (okButton.Selected)
|
|
{
|
|
saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(passwordBox.Text)));
|
|
saltedPw = saltedPw + Convert.ToString(nonce);
|
|
saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(saltedPw)));
|
|
|
|
timeOut = DateTime.Now + new TimeSpan(0, 0, 20);
|
|
reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
|
|
|
msgBox.Close(null, null);
|
|
break;
|
|
}
|
|
else if (cancelButton.Selected)
|
|
{
|
|
msgBox.Close(null, null);
|
|
connectCancelled = true;
|
|
}
|
|
else
|
|
{
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reconnectBox != null)
|
|
{
|
|
reconnectBox.Close(null, null);
|
|
reconnectBox = null;
|
|
}
|
|
|
|
if (connectCancelled) yield return CoroutineStatus.Success;
|
|
|
|
if (client.ConnectionStatus != NetConnectionStatus.Connected)
|
|
{
|
|
steamAuthTicket?.Cancel();
|
|
steamAuthTicket = null;
|
|
var reconnect = new GUIMessageBox(TextManager.Get("ConnectionFailed"), TextManager.Get("CouldNotConnectToServer"), new string[] { TextManager.Get("Retry"), TextManager.Get("Cancel") });
|
|
|
|
DebugConsole.NewMessage("Failed to connect to the server - connection status: " + client.ConnectionStatus.ToString(), Color.Orange);
|
|
|
|
reconnect.Buttons[0].OnClicked += RetryConnection;
|
|
reconnect.Buttons[0].OnClicked += reconnect.Close;
|
|
reconnect.Buttons[1].OnClicked += ReturnToServerList;
|
|
reconnect.Buttons[1].OnClicked += reconnect.Close;
|
|
}
|
|
else
|
|
{
|
|
if (Screen.Selected != GameMain.GameScreen)
|
|
{
|
|
GameMain.NetLobbyScreen.Select();
|
|
}
|
|
connected = true;
|
|
}
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
private void WriteAuthRequest(NetOutgoingMessage outmsg)
|
|
{
|
|
if (SteamManager.IsInitialized && SteamManager.USE_STEAM)
|
|
{
|
|
if (steamAuthTicket == null)
|
|
{
|
|
steamAuthTicket = SteamManager.GetAuthSessionTicket();
|
|
}
|
|
|
|
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 3);
|
|
while ((steamAuthTicket.Data == null || steamAuthTicket.Data.Length == 0) &&
|
|
DateTime.Now < timeOut)
|
|
{
|
|
System.Threading.Thread.Sleep(10);
|
|
}
|
|
|
|
outmsg.Write((byte)ClientPacketHeader.REQUEST_STEAMAUTH);
|
|
outmsg.Write(SteamManager.GetSteamID());
|
|
outmsg.Write(steamAuthTicket.Data.Length);
|
|
outmsg.Write(steamAuthTicket.Data);
|
|
|
|
DebugConsole.Log("Sending Steam auth request");
|
|
DebugConsole.Log(" Steam ID: " + SteamManager.GetSteamID());
|
|
DebugConsole.Log(" Ticket data: " +
|
|
ToolBox.LimitString(string.Concat(steamAuthTicket.Data.Select(b => b.ToString("X2"))), 16));
|
|
DebugConsole.Log(" Msg length: " + outmsg.LengthBytes);
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.Log("Sending auth request");
|
|
outmsg.Write((byte)ClientPacketHeader.REQUEST_AUTH);
|
|
}
|
|
}
|
|
|
|
public override void Update(float deltaTime)
|
|
{
|
|
#if DEBUG
|
|
if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return;
|
|
#endif
|
|
if (gameStarted) SetRadioButtonColor();
|
|
|
|
base.Update(deltaTime);
|
|
|
|
if (!connected) return;
|
|
|
|
if (reconnectBox != null)
|
|
{
|
|
reconnectBox.Close(null, null);
|
|
reconnectBox = null;
|
|
}
|
|
|
|
try
|
|
{
|
|
CheckServerMessages();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
string errorMsg = "Error while reading a message from server. {" + e + "}\n" + e.StackTrace;
|
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
|
DebugConsole.ThrowError("Error while reading a message from server.", e);
|
|
new GUIMessageBox(TextManager.Get("Error"), "Error while reading a message from the server. (" + e.Message + " Target site: " + e.TargetSite + ")");
|
|
Disconnect();
|
|
GameMain.MainMenuScreen.Select();
|
|
return;
|
|
}
|
|
|
|
if (gameStarted && Screen.Selected == GameMain.GameScreen)
|
|
{
|
|
endVoteTickBox.Visible = Voting.AllowEndVoting && HasSpawned;
|
|
|
|
if (respawnManager != null)
|
|
{
|
|
respawnManager.Update(deltaTime);
|
|
}
|
|
|
|
if (updateTimer > DateTime.Now) return;
|
|
SendIngameUpdate();
|
|
}
|
|
else
|
|
{
|
|
if (updateTimer > DateTime.Now) return;
|
|
SendLobbyUpdate();
|
|
}
|
|
|
|
// Update current time
|
|
updateTimer = DateTime.Now + updateInterval;
|
|
}
|
|
|
|
private CoroutineHandle startGameCoroutine;
|
|
|
|
private void DecompressIncomingMessage(NetIncomingMessage inc)
|
|
{
|
|
byte[] data = inc.Data;
|
|
if (data[data.Length - 1] == 1)
|
|
{
|
|
using (MemoryStream stream = new MemoryStream())
|
|
{
|
|
stream.Write(data, 0, inc.LengthBytes-1);
|
|
stream.Position = 0;
|
|
using (MemoryStream decompressed = new MemoryStream())
|
|
{
|
|
using (DeflateStream deflate = new DeflateStream(stream, CompressionMode.Decompress, false))
|
|
{
|
|
deflate.CopyTo(decompressed);
|
|
}
|
|
byte[] newData = decompressed.ToArray();
|
|
|
|
inc.Data = newData;
|
|
inc.LengthBytes = newData.Length;
|
|
inc.Position = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check for new incoming messages from server
|
|
/// </summary>
|
|
private void CheckServerMessages()
|
|
{
|
|
// Create new incoming message holder
|
|
NetIncomingMessage inc;
|
|
|
|
if (startGameCoroutine != null && CoroutineManager.IsCoroutineRunning(startGameCoroutine)) return;
|
|
|
|
while ((inc = client.ReadMessage()) != null)
|
|
{
|
|
switch (inc.MessageType)
|
|
{
|
|
case NetIncomingMessageType.Data:
|
|
DecompressIncomingMessage(inc);
|
|
|
|
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
|
|
switch (header)
|
|
{
|
|
case ServerPacketHeader.UPDATE_LOBBY:
|
|
ReadLobbyUpdate(inc);
|
|
break;
|
|
case ServerPacketHeader.UPDATE_INGAME:
|
|
ReadIngameUpdate(inc);
|
|
break;
|
|
case ServerPacketHeader.QUERY_STARTGAME:
|
|
string subName = inc.ReadString();
|
|
string subHash = inc.ReadString();
|
|
|
|
bool usingShuttle = inc.ReadBoolean();
|
|
string shuttleName = inc.ReadString();
|
|
string shuttleHash = inc.ReadString();
|
|
|
|
NetOutgoingMessage readyToStartMsg = client.CreateMessage();
|
|
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
|
|
|
|
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
|
|
readyToStartMsg.Write(
|
|
GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList) &&
|
|
GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox));
|
|
|
|
WriteCharacterInfo(readyToStartMsg);
|
|
|
|
client.SendMessage(readyToStartMsg, NetDeliveryMethod.ReliableUnordered);
|
|
|
|
break;
|
|
case ServerPacketHeader.STARTGAME:
|
|
startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false);
|
|
break;
|
|
case ServerPacketHeader.ENDGAME:
|
|
string endMessage = inc.ReadString();
|
|
bool missionSuccessful = inc.ReadBoolean();
|
|
if (missionSuccessful && GameMain.GameSession?.Mission != null)
|
|
{
|
|
GameMain.GameSession.Mission.Completed = true;
|
|
}
|
|
CoroutineManager.StartCoroutine(EndGame(endMessage));
|
|
break;
|
|
case ServerPacketHeader.PERMISSIONS:
|
|
ReadPermissions(inc);
|
|
break;
|
|
case ServerPacketHeader.ACHIEVEMENT:
|
|
ReadAchievement(inc);
|
|
break;
|
|
case ServerPacketHeader.FILE_TRANSFER:
|
|
fileReceiver.ReadMessage(inc);
|
|
break;
|
|
}
|
|
break;
|
|
case NetIncomingMessageType.StatusChanged:
|
|
NetConnectionStatus connectionStatus = (NetConnectionStatus)inc.ReadByte();
|
|
DebugConsole.NewMessage("Connection status changed: " + connectionStatus.ToString(), Color.Orange);
|
|
|
|
if (connectionStatus == NetConnectionStatus.Disconnected)
|
|
{
|
|
ReadDisconnectMessage(inc, true);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReadDisconnectMessage(NetIncomingMessage inc, bool allowReconnect)
|
|
{
|
|
steamAuthTicket?.Cancel();
|
|
steamAuthTicket = null;
|
|
|
|
string disconnectMsg = inc.ReadString();
|
|
string[] splitMsg = disconnectMsg.Split(';');
|
|
DisconnectReason disconnectReason = DisconnectReason.Unknown;
|
|
if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason);
|
|
|
|
if (disconnectReason == DisconnectReason.ServerFull)
|
|
{
|
|
//already waiting for a slot to free up, do nothing
|
|
if (CoroutineManager.IsCoroutineRunning("WaitInServerQueue")) return;
|
|
|
|
reconnectBox?.Close(); reconnectBox = null;
|
|
|
|
var queueBox = new GUIMessageBox(
|
|
TextManager.Get("DisconnectReason.ServerFull"),
|
|
TextManager.Get("ServerFullQuestionPrompt"), new string[] { TextManager.Get("Cancel"), TextManager.Get("ServerQueue") });
|
|
|
|
queueBox.Buttons[0].OnClicked += queueBox.Close;
|
|
queueBox.Buttons[1].OnClicked += queueBox.Close;
|
|
queueBox.Buttons[1].OnClicked += (btn, userdata) =>
|
|
{
|
|
reconnectBox?.Close(); reconnectBox = null;
|
|
CoroutineManager.StartCoroutine(WaitInServerQueue(), "WaitInServerQueue");
|
|
return true;
|
|
};
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//disconnected/denied for some other reason than the server being full
|
|
// -> stop queuing and show a message box
|
|
waitInServerQueueBox?.Close();
|
|
waitInServerQueueBox = null;
|
|
CoroutineManager.StopCoroutines("WaitInServerQueue");
|
|
}
|
|
|
|
if (allowReconnect && disconnectReason == DisconnectReason.Unknown)
|
|
{
|
|
reconnectBox = new GUIMessageBox(
|
|
TextManager.Get("ConnectionLost"),
|
|
TextManager.Get("ConnectionLostReconnecting"), new string[0]);
|
|
connected = false;
|
|
ConnectToServer(serverIP);
|
|
}
|
|
else
|
|
{
|
|
string msg = "";
|
|
if (disconnectReason == DisconnectReason.Unknown)
|
|
{
|
|
msg = disconnectMsg;
|
|
}
|
|
else
|
|
{
|
|
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
|
|
for (int i = 1; i < splitMsg.Length; i++)
|
|
{
|
|
msg += splitMsg[i];
|
|
}
|
|
}
|
|
var msgBox = new GUIMessageBox(TextManager.Get(allowReconnect ? "ConnectionLost" : "CouldNotConnectToServer"), msg);
|
|
msgBox.Buttons[0].OnClicked += ReturnToServerList;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> WaitInServerQueue()
|
|
{
|
|
waitInServerQueueBox = new GUIMessageBox(
|
|
TextManager.Get("ServerQueuePleaseWait"),
|
|
TextManager.Get("WaitingInServerQueue"), new string[] { TextManager.Get("Cancel") });
|
|
waitInServerQueueBox.Buttons[0].OnClicked += (btn, userdata) =>
|
|
{
|
|
CoroutineManager.StopCoroutines("WaitInServerQueue");
|
|
waitInServerQueueBox?.Close();
|
|
waitInServerQueueBox = null;
|
|
return true;
|
|
};
|
|
|
|
while (!connected)
|
|
{
|
|
if (!CoroutineManager.IsCoroutineRunning("WaitForStartingInfo"))
|
|
{
|
|
ConnectToServer(serverIP);
|
|
yield return new WaitForSeconds(2.0f);
|
|
}
|
|
yield return new WaitForSeconds(0.5f);
|
|
}
|
|
|
|
waitInServerQueueBox?.Close();
|
|
waitInServerQueueBox = null;
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
|
|
private void ReadAchievement(NetIncomingMessage inc)
|
|
{
|
|
string achievementIdentifier = inc.ReadString();
|
|
SteamAchievementManager.UnlockAchievement(achievementIdentifier);
|
|
}
|
|
|
|
private void ReadPermissions(NetIncomingMessage inc)
|
|
{
|
|
List<string> permittedConsoleCommands = new List<string>();
|
|
ClientPermissions newPermissions = (ClientPermissions)inc.ReadUInt16();
|
|
if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands))
|
|
{
|
|
UInt16 consoleCommandCount = inc.ReadUInt16();
|
|
for (int i = 0; i < consoleCommandCount; i++)
|
|
{
|
|
permittedConsoleCommands.Add(inc.ReadString());
|
|
}
|
|
}
|
|
|
|
SetPermissions(newPermissions, permittedConsoleCommands);
|
|
}
|
|
|
|
private void SetPermissions(ClientPermissions newPermissions, List<string> permittedConsoleCommands)
|
|
{
|
|
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
|
|
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
|
|
{
|
|
if (newPermissions == permissions) return;
|
|
}
|
|
|
|
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "permissions");
|
|
|
|
string msg = "";
|
|
if (newPermissions == ClientPermissions.None)
|
|
{
|
|
msg = TextManager.Get("PermissionsRemoved");
|
|
}
|
|
else
|
|
{
|
|
msg = TextManager.Get("CurrentPermissions") + '\n';
|
|
foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions)))
|
|
{
|
|
if (!newPermissions.HasFlag(permission) || permission == ClientPermissions.None) continue;
|
|
msg += " - " + TextManager.Get("ClientPermission." + permission) + "\n";
|
|
}
|
|
}
|
|
|
|
permissions = newPermissions;
|
|
this.permittedConsoleCommands = new List<string>(permittedConsoleCommands);
|
|
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("PermissionsChanged"), msg, GUIMessageBox.DefaultWidth, 0)
|
|
{
|
|
UserData = "permissions"
|
|
};
|
|
|
|
if (newPermissions.HasFlag(ClientPermissions.ConsoleCommands))
|
|
{
|
|
int listBoxWidth = (int)(msgBox.InnerFrame.Rect.Width) / 2 - 30;
|
|
new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.1f), msgBox.InnerFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.05f, 0.1f) },
|
|
TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUI.SmallFont);
|
|
var commandList = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.6f), msgBox.InnerFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.05f, 0.2f) });
|
|
foreach (string permittedCommand in permittedConsoleCommands)
|
|
{
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), commandList.Content.RectTransform, minSize: new Point(0, 15)),
|
|
permittedCommand, font: GUI.SmallFont)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
}
|
|
}
|
|
|
|
GameMain.NetLobbyScreen.SubList.Enabled = Voting.AllowSubVoting || HasPermission(ClientPermissions.SelectSub);
|
|
GameMain.NetLobbyScreen.ModeList.Enabled = Voting.AllowModeVoting || HasPermission(ClientPermissions.SelectMode);
|
|
GameMain.NetLobbyScreen.ShowLogButton.Visible = HasPermission(ClientPermissions.ServerLog);
|
|
showLogButton.Visible = HasPermission(ClientPermissions.ServerLog);
|
|
|
|
endRoundButton.Visible = HasPermission(ClientPermissions.EndRound);
|
|
}
|
|
|
|
private IEnumerable<object> StartGame(NetIncomingMessage inc)
|
|
{
|
|
if (Character != null) Character.Remove();
|
|
HasSpawned = false;
|
|
|
|
GameMain.LightManager.LightingEnabled = true;
|
|
|
|
//enable spectate button in case we fail to start the round now
|
|
//(for example, due to a missing sub file or an error)
|
|
GameMain.NetLobbyScreen.ShowSpectateButton();
|
|
|
|
entityEventManager.Clear();
|
|
LastSentEntityEventID = 0;
|
|
|
|
endVoteTickBox.Selected = false;
|
|
|
|
int seed = inc.ReadInt32();
|
|
string levelSeed = inc.ReadString();
|
|
float levelDifficulty = inc.ReadFloat();
|
|
|
|
byte losMode = inc.ReadByte();
|
|
|
|
int missionTypeIndex = inc.ReadByte();
|
|
|
|
string subName = inc.ReadString();
|
|
string subHash = inc.ReadString();
|
|
|
|
bool usingShuttle = inc.ReadBoolean();
|
|
string shuttleName = inc.ReadString();
|
|
string shuttleHash = inc.ReadString();
|
|
|
|
string modeIdentifier = inc.ReadString();
|
|
int missionIndex = inc.ReadInt16();
|
|
|
|
bool respawnAllowed = inc.ReadBoolean();
|
|
bool loadSecondSub = inc.ReadBoolean();
|
|
|
|
bool disguisesAllowed = inc.ReadBoolean();
|
|
bool isTraitor = inc.ReadBoolean();
|
|
string traitorTargetName = isTraitor ? inc.ReadString() : null;
|
|
|
|
//monster spawn settings
|
|
if (monsterEnabled == null)
|
|
{
|
|
List<string> monsterNames1 = GameMain.Instance.GetFilesOfType(ContentType.Character).ToList();
|
|
for (int i = 0; i < monsterNames1.Count; i++)
|
|
{
|
|
monsterNames1[i] = Path.GetFileName(Path.GetDirectoryName(monsterNames1[i]));
|
|
}
|
|
|
|
monsterEnabled = new Dictionary<string, bool>();
|
|
foreach (string s in monsterNames1)
|
|
{
|
|
if (!monsterEnabled.ContainsKey(s)) monsterEnabled.Add(s, true);
|
|
}
|
|
}
|
|
|
|
List<string> monsterNames = monsterEnabled.Keys.ToList();
|
|
foreach (string s in monsterNames)
|
|
{
|
|
monsterEnabled[s] = inc.ReadBoolean();
|
|
}
|
|
inc.ReadPadBits();
|
|
|
|
GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
|
|
MultiPlayerCampaign campaign = GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
|
|
GameMain.GameSession?.GameMode as MultiPlayerCampaign : null;
|
|
|
|
if (gameMode == null)
|
|
{
|
|
DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!");
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
|
|
GameMain.LightManager.LosMode = (LosMode)losMode;
|
|
|
|
AllowDisguises = disguisesAllowed;
|
|
|
|
if (campaign == null)
|
|
{
|
|
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList))
|
|
{
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox))
|
|
{
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
}
|
|
|
|
Rand.SetSyncedSeed(seed);
|
|
|
|
if (campaign == null)
|
|
{
|
|
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionIndex < 0 ? null : MissionPrefab.List[missionIndex]);
|
|
GameMain.GameSession.StartRound(levelSeed, levelDifficulty, loadSecondSub);
|
|
}
|
|
else
|
|
{
|
|
if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset();
|
|
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
|
|
reloadSub: true,
|
|
loadSecondSub: false,
|
|
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
|
|
}
|
|
|
|
if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null);
|
|
|
|
gameStarted = true;
|
|
|
|
GameMain.GameScreen.Select();
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
public IEnumerable<object> EndGame(string endMessage)
|
|
{
|
|
if (!gameStarted) yield return CoroutineStatus.Success;
|
|
|
|
if (GameMain.GameSession != null) GameMain.GameSession.GameMode.End(endMessage);
|
|
|
|
gameStarted = false;
|
|
Character.Controlled = null;
|
|
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
|
GameMain.LightManager.LosEnabled = false;
|
|
respawnManager = null;
|
|
|
|
float endPreviewLength = 10.0f;
|
|
if (Screen.Selected == GameMain.GameScreen)
|
|
{
|
|
new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength);
|
|
float secondsLeft = endPreviewLength;
|
|
do
|
|
{
|
|
secondsLeft -= CoroutineManager.UnscaledDeltaTime;
|
|
yield return CoroutineStatus.Running;
|
|
} while (secondsLeft > 0.0f);
|
|
}
|
|
|
|
Submarine.Unload();
|
|
GameMain.NetLobbyScreen.Select();
|
|
myCharacter = null;
|
|
foreach (Client c in otherClients)
|
|
{
|
|
c.InGame = false;
|
|
c.Character = null;
|
|
}
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
private void ReadInitialUpdate(NetIncomingMessage inc)
|
|
{
|
|
myID = inc.ReadByte();
|
|
|
|
UInt16 subListCount = inc.ReadUInt16();
|
|
List<Submarine> submarines = new List<Submarine>();
|
|
for (int i = 0; i < subListCount; i++)
|
|
{
|
|
string subName = inc.ReadString();
|
|
string subHash = inc.ReadString();
|
|
|
|
var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash);
|
|
if (matchingSub != null)
|
|
{
|
|
submarines.Add(matchingSub);
|
|
}
|
|
else
|
|
{
|
|
submarines.Add(new Submarine(Path.Combine(Submarine.SavePath, subName) + ".sub", subHash, false));
|
|
}
|
|
}
|
|
|
|
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, submarines);
|
|
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, submarines);
|
|
|
|
gameStarted = inc.ReadBoolean();
|
|
bool allowSpectating = inc.ReadBoolean();
|
|
|
|
ReadPermissions(inc);
|
|
|
|
if (gameStarted)
|
|
{
|
|
new GUIMessageBox("Please wait", TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled"));
|
|
GameMain.NetLobbyScreen.Select();
|
|
}
|
|
}
|
|
|
|
private void ReadLobbyUpdate(NetIncomingMessage inc)
|
|
{
|
|
ServerNetObject objHeader;
|
|
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
|
|
{
|
|
switch (objHeader)
|
|
{
|
|
case ServerNetObject.SYNC_IDS:
|
|
bool lobbyUpdated = inc.ReadBoolean();
|
|
inc.ReadPadBits();
|
|
|
|
if (lobbyUpdated)
|
|
{
|
|
var prevDispatcher = GUI.KeyboardDispatcher.Subscriber;
|
|
|
|
UInt16 updateID = inc.ReadUInt16();
|
|
string serverName = inc.ReadString();
|
|
string serverText = inc.ReadString();
|
|
|
|
if (inc.ReadBoolean())
|
|
{
|
|
if (GameSettings.VerboseLogging)
|
|
{
|
|
DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray);
|
|
}
|
|
ReadInitialUpdate(inc);
|
|
}
|
|
|
|
string selectSubName = inc.ReadString();
|
|
string selectSubHash = inc.ReadString();
|
|
|
|
bool usingShuttle = inc.ReadBoolean();
|
|
string selectShuttleName = inc.ReadString();
|
|
string selectShuttleHash = inc.ReadString();
|
|
|
|
bool allowSubVoting = inc.ReadBoolean();
|
|
bool allowModeVoting = inc.ReadBoolean();
|
|
|
|
bool allowSpectating = inc.ReadBoolean();
|
|
|
|
YesNoMaybe traitorsEnabled = (YesNoMaybe)inc.ReadRangedInteger(0, 2);
|
|
int missionTypeIndex = inc.ReadRangedInteger(0, Enum.GetValues(typeof(MissionType)).Length - 1);
|
|
int modeIndex = inc.ReadByte();
|
|
|
|
string levelSeed = inc.ReadString();
|
|
float levelDifficulty = inc.ReadFloat();
|
|
|
|
byte botCount = inc.ReadByte();
|
|
BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal;
|
|
|
|
bool autoRestartEnabled = inc.ReadBoolean();
|
|
float autoRestartTimer = autoRestartEnabled ? inc.ReadFloat() : 0.0f;
|
|
|
|
int clientCount = inc.ReadByte();
|
|
List<string> clientNames = new List<string>();
|
|
List<byte> clientIDs = new List<byte>();
|
|
List<ushort> characterIDs = new List<ushort>();
|
|
for (int i = 0; i < clientCount; i++)
|
|
{
|
|
clientIDs.Add(inc.ReadByte());
|
|
clientNames.Add(inc.ReadString());
|
|
characterIDs.Add(inc.ReadUInt16());
|
|
}
|
|
|
|
//ignore the message if we already a more up-to-date one
|
|
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID))
|
|
{
|
|
GameMain.NetLobbyScreen.LastUpdateID = updateID;
|
|
|
|
ServerLog.ServerName = serverName;
|
|
|
|
GameMain.NetLobbyScreen.ServerName = serverName;
|
|
GameMain.NetLobbyScreen.ServerMessage.Text = serverText;
|
|
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
|
|
|
|
if (!allowSubVoting) GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, GameMain.NetLobbyScreen.SubList);
|
|
GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
|
|
|
|
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
|
|
GameMain.NetLobbyScreen.SetMissionType(missionTypeIndex);
|
|
|
|
if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex);
|
|
|
|
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
|
|
GameMain.NetLobbyScreen.LevelSeed = levelSeed;
|
|
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
|
|
GameMain.NetLobbyScreen.SetBotCount(botCount);
|
|
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
|
|
GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer);
|
|
|
|
List<Client> currentClients = new List<Client>();
|
|
for (int i = 0; i < clientNames.Count; i++)
|
|
{
|
|
//see if the client already exists
|
|
var existingClient = ConnectedClients.Find(c => c.ID == clientIDs[i] && c.Name == clientNames[i]);
|
|
if (existingClient == null) //if not, create it
|
|
{
|
|
existingClient = new Client(clientNames[i], clientIDs[i]);
|
|
ConnectedClients.Add(existingClient);
|
|
GameMain.NetLobbyScreen.AddPlayer(existingClient.Name);
|
|
}
|
|
if (characterIDs[i] > 0)
|
|
{
|
|
existingClient.Character = Entity.FindEntityByID(characterIDs[i]) as Character;
|
|
}
|
|
currentClients.Add(existingClient);
|
|
}
|
|
//remove clients that aren't present anymore
|
|
for (int i = ConnectedClients.Count - 1; i >= 0; i--)
|
|
{
|
|
if (!currentClients.Contains(ConnectedClients[i]))
|
|
{
|
|
GameMain.NetLobbyScreen.RemovePlayer(ConnectedClients[i].Name);
|
|
ConnectedClients.RemoveAt(i);
|
|
}
|
|
}
|
|
//remove clients that aren't present anymore
|
|
for (int i = ConnectedClients.Count - 1; i >= 0; i--)
|
|
{
|
|
if (!currentClients.Contains(ConnectedClients[i]))
|
|
{
|
|
GameMain.NetLobbyScreen.RemovePlayer(ConnectedClients[i].Name);
|
|
ConnectedClients.RemoveAt(i);
|
|
}
|
|
}
|
|
//remove clients that aren't present anymore
|
|
for (int i = ConnectedClients.Count - 1; i >= 0; i--)
|
|
{
|
|
if (!currentClients.Contains(ConnectedClients[i]))
|
|
{
|
|
GameMain.NetLobbyScreen.RemovePlayer(ConnectedClients[i].Name);
|
|
ConnectedClients.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
Voting.AllowSubVoting = allowSubVoting;
|
|
Voting.AllowModeVoting = allowModeVoting;
|
|
GUI.KeyboardDispatcher.Subscriber = prevDispatcher;
|
|
}
|
|
}
|
|
|
|
bool campaignUpdated = inc.ReadBoolean();
|
|
inc.ReadPadBits();
|
|
if (campaignUpdated)
|
|
{
|
|
MultiPlayerCampaign.ClientRead(inc);
|
|
}
|
|
|
|
lastSentChatMsgID = inc.ReadUInt16();
|
|
|
|
break;
|
|
case ServerNetObject.CHAT_MESSAGE:
|
|
ChatMessage.ClientRead(inc);
|
|
break;
|
|
case ServerNetObject.VOTE:
|
|
Voting.ClientRead(inc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReadIngameUpdate(NetIncomingMessage inc)
|
|
{
|
|
List<IServerSerializable> entities = new List<IServerSerializable>();
|
|
|
|
float sendingTime = inc.ReadFloat() - inc.SenderConnection.RemoteTimeOffset;
|
|
|
|
ServerNetObject? prevObjHeader = null;
|
|
long prevBitPos = 0;
|
|
long prevBytePos = 0;
|
|
|
|
long prevBitLength = 0;
|
|
long prevByteLength = 0;
|
|
|
|
ServerNetObject objHeader;
|
|
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
|
|
{
|
|
switch (objHeader)
|
|
{
|
|
case ServerNetObject.SYNC_IDS:
|
|
lastSentChatMsgID = inc.ReadUInt16();
|
|
LastSentEntityEventID = inc.ReadUInt16();
|
|
break;
|
|
case ServerNetObject.ENTITY_POSITION:
|
|
UInt16 id = inc.ReadUInt16();
|
|
byte msgLength = inc.ReadByte();
|
|
|
|
long msgEndPos = inc.Position + msgLength * 8;
|
|
|
|
var entity = Entity.FindEntityByID(id) as IServerSerializable;
|
|
if (entity != null)
|
|
{
|
|
entity.ClientRead(objHeader, inc, sendingTime);
|
|
}
|
|
|
|
//force to the correct position in case the entity doesn't exist
|
|
//or the message wasn't read correctly for whatever reason
|
|
inc.Position = msgEndPos;
|
|
inc.ReadPadBits();
|
|
break;
|
|
case ServerNetObject.ENTITY_EVENT:
|
|
case ServerNetObject.ENTITY_EVENT_INITIAL:
|
|
entityEventManager.Read(objHeader, inc, sendingTime, entities);
|
|
break;
|
|
case ServerNetObject.CHAT_MESSAGE:
|
|
ChatMessage.ClientRead(inc);
|
|
break;
|
|
default:
|
|
List<string> errorLines = new List<string>
|
|
{
|
|
"Error while reading update from server (unknown object header \"" + objHeader + "\"!)",
|
|
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
|
|
prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!",
|
|
"Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)"
|
|
};
|
|
if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL)
|
|
{
|
|
foreach (IServerSerializable ent in entities)
|
|
{
|
|
if (ent == null)
|
|
{
|
|
errorLines.Add(" - NULL");
|
|
continue;
|
|
}
|
|
Entity e = ent as Entity;
|
|
errorLines.Add(" - " + e.ToString());
|
|
}
|
|
}
|
|
|
|
foreach (string line in errorLines)
|
|
{
|
|
DebugConsole.ThrowError(line);
|
|
}
|
|
errorLines.Add("Last console messages:");
|
|
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
|
|
{
|
|
errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
|
|
}
|
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines));
|
|
|
|
DebugConsole.ThrowError("Writing object data to \"crashreport_object.bin\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues");
|
|
|
|
FileStream fl = File.Open("crashreport_object.bin", FileMode.Create);
|
|
BinaryWriter sw = new BinaryWriter(fl);
|
|
|
|
sw.Write(inc.Data, (int)(prevBytePos - prevByteLength), (int)(prevByteLength));
|
|
|
|
sw.Close();
|
|
fl.Close();
|
|
|
|
throw new Exception("Error while reading update from server: please send us \"crashreport_object.bin\"!");
|
|
}
|
|
prevBitLength = inc.Position - prevBitPos;
|
|
prevByteLength = inc.PositionInBytes - prevByteLength;
|
|
|
|
prevObjHeader = objHeader;
|
|
prevBitPos = inc.Position;
|
|
prevBytePos = inc.PositionInBytes;
|
|
}
|
|
}
|
|
|
|
private void SendLobbyUpdate()
|
|
{
|
|
NetOutgoingMessage outmsg = client.CreateMessage();
|
|
outmsg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
|
|
|
|
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
|
|
outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
|
outmsg.Write(ChatMessage.LastID);
|
|
|
|
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
|
|
if (campaign == null || campaign.LastSaveID == 0)
|
|
{
|
|
outmsg.Write((UInt16)0);
|
|
}
|
|
else
|
|
{
|
|
outmsg.Write(campaign.LastSaveID);
|
|
outmsg.Write(campaign.CampaignID);
|
|
outmsg.Write(campaign.LastUpdateID);
|
|
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
|
}
|
|
|
|
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
|
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
|
{
|
|
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > client.Configuration.MaximumTransmissionUnit - 5)
|
|
{
|
|
//not enough room in this packet
|
|
return;
|
|
}
|
|
chatMsgQueue[i].ClientWrite(outmsg);
|
|
}
|
|
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
|
|
|
|
if (outmsg.LengthBytes > client.Configuration.MaximumTransmissionUnit)
|
|
{
|
|
DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + client.Configuration.MaximumTransmissionUnit);
|
|
}
|
|
|
|
client.SendMessage(outmsg, NetDeliveryMethod.Unreliable);
|
|
}
|
|
|
|
private void SendIngameUpdate()
|
|
{
|
|
NetOutgoingMessage outmsg = client.CreateMessage();
|
|
outmsg.Write((byte)ClientPacketHeader.UPDATE_INGAME);
|
|
|
|
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
|
|
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
|
outmsg.Write(ChatMessage.LastID);
|
|
outmsg.Write(entityEventManager.LastReceivedID);
|
|
|
|
Character.Controlled?.ClientWrite(outmsg);
|
|
|
|
entityEventManager.Write(outmsg, client.ServerConnection);
|
|
|
|
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
|
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
|
{
|
|
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > client.Configuration.MaximumTransmissionUnit - 5)
|
|
{
|
|
//not enough room in this packet
|
|
return;
|
|
}
|
|
chatMsgQueue[i].ClientWrite(outmsg);
|
|
}
|
|
|
|
outmsg.Write((byte)ClientNetObject.END_OF_MESSAGE);
|
|
|
|
if (outmsg.LengthBytes > client.Configuration.MaximumTransmissionUnit)
|
|
{
|
|
DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + client.Configuration.MaximumTransmissionUnit);
|
|
}
|
|
|
|
client.SendMessage(outmsg, NetDeliveryMethod.Unreliable);
|
|
}
|
|
|
|
public void SendChatMessage(ChatMessage msg)
|
|
{
|
|
if (client.ServerConnection == null) return;
|
|
lastQueueChatMsgID++;
|
|
msg.NetStateID = lastQueueChatMsgID;
|
|
chatMsgQueue.Add(msg);
|
|
}
|
|
|
|
public void SendChatMessage(string message, ChatMessageType type = ChatMessageType.Default)
|
|
{
|
|
if (client.ServerConnection == null) return;
|
|
|
|
ChatMessage chatMessage = ChatMessage.Create(
|
|
gameStarted && myCharacter != null ? myCharacter.Name : name,
|
|
message,
|
|
type,
|
|
gameStarted && myCharacter != null ? myCharacter : null);
|
|
|
|
lastQueueChatMsgID++;
|
|
chatMessage.NetStateID = lastQueueChatMsgID;
|
|
|
|
chatMsgQueue.Add(chatMessage);
|
|
}
|
|
|
|
public void RequestFile(FileTransferType fileType, string file, string fileHash)
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
|
|
msg.Write((byte)FileTransferMessageType.Initiate);
|
|
msg.Write((byte)fileType);
|
|
if (file != null) msg.Write(file);
|
|
if (fileHash != null) msg.Write(fileHash);
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public void CancelFileTransfer(FileReceiver.FileTransferIn transfer)
|
|
{
|
|
CancelFileTransfer(transfer.SequenceChannel);
|
|
}
|
|
|
|
public void CancelFileTransfer(int sequenceChannel)
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.FILE_REQUEST);
|
|
msg.Write((byte)FileTransferMessageType.Cancel);
|
|
msg.Write((byte)sequenceChannel);
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
private void OnFileReceived(FileReceiver.FileTransferIn transfer)
|
|
{
|
|
switch (transfer.FileType)
|
|
{
|
|
case FileTransferType.Submarine:
|
|
new GUIMessageBox(TextManager.Get("ServerDownloadFinished"), TextManager.Get("FileDownloadedNotification").Replace("[filename]", transfer.FileName));
|
|
var newSub = new Submarine(transfer.FilePath);
|
|
var existingSubs = Submarine.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.Hash == newSub.MD5Hash.Hash).ToList();
|
|
foreach (Submarine existingSub in existingSubs)
|
|
{
|
|
existingSub.Dispose();
|
|
}
|
|
Submarine.AddToSavedSubs(newSub);
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
IEnumerable<GUIComponent> subListChildren = (i == 0) ?
|
|
GameMain.NetLobbyScreen.ShuttleList.ListBox.Content.Children :
|
|
GameMain.NetLobbyScreen.SubList.Content.Children;
|
|
|
|
var subElement = subListChildren.FirstOrDefault(c =>
|
|
((Submarine)c.UserData).Name == newSub.Name &&
|
|
((Submarine)c.UserData).MD5Hash.Hash == newSub.MD5Hash.Hash);
|
|
if (subElement == null) continue;
|
|
|
|
subElement.GetChild<GUITextBlock>().TextColor = new Color(subElement.GetChild<GUITextBlock>().TextColor, 1.0f);
|
|
subElement.UserData = newSub;
|
|
subElement.ToolTip = newSub.Description;
|
|
|
|
GUIButton infoButton = subElement.GetChild<GUIButton>();
|
|
if (infoButton == null)
|
|
{
|
|
int buttonSize = (int)(subElement.Rect.Height * 0.8f);
|
|
infoButton = new GUIButton(new RectTransform(new Point(buttonSize), subElement.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point((int)(buttonSize * 0.2f), 0) }, "?");
|
|
}
|
|
infoButton.UserData = newSub;
|
|
infoButton.OnClicked = (component, userdata) =>
|
|
{
|
|
((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 400));
|
|
return true;
|
|
};
|
|
}
|
|
|
|
if (GameMain.NetLobbyScreen.FailedSelectedSub != null &&
|
|
GameMain.NetLobbyScreen.FailedSelectedSub.First == newSub.Name &&
|
|
GameMain.NetLobbyScreen.FailedSelectedSub.Second == newSub.MD5Hash.Hash)
|
|
{
|
|
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.SubList);
|
|
}
|
|
|
|
if (GameMain.NetLobbyScreen.FailedSelectedShuttle != null &&
|
|
GameMain.NetLobbyScreen.FailedSelectedShuttle.First == newSub.Name &&
|
|
GameMain.NetLobbyScreen.FailedSelectedShuttle.Second == newSub.MD5Hash.Hash)
|
|
{
|
|
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
|
|
}
|
|
|
|
break;
|
|
case FileTransferType.CampaignSave:
|
|
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
|
|
if (campaign == null) return;
|
|
|
|
GameMain.GameSession.SavePath = transfer.FilePath;
|
|
if (GameMain.GameSession.Submarine == null)
|
|
{
|
|
var gameSessionDoc = SaveUtil.LoadGameSessionDoc(GameMain.GameSession.SavePath);
|
|
string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub";
|
|
GameMain.GameSession.Submarine = new Submarine(subPath, "");
|
|
}
|
|
|
|
SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession);
|
|
campaign.LastSaveID = campaign.PendingSaveID;
|
|
//decrement campaign update ID so the server will send us the latest data
|
|
//(as there may have been campaign updates after the save file was created)
|
|
campaign.LastUpdateID--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void OnTransferFailed(FileReceiver.FileTransferIn transfer)
|
|
{
|
|
if (transfer.FileType == FileTransferType.CampaignSave)
|
|
{
|
|
GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null);
|
|
}
|
|
}
|
|
|
|
public void CreateEntityEvent(IClientSerializable entity, object[] extraData)
|
|
{
|
|
entityEventManager.CreateEvent(entity, extraData);
|
|
}
|
|
|
|
public bool HasPermission(ClientPermissions permission)
|
|
{
|
|
return permissions.HasFlag(permission);
|
|
}
|
|
|
|
public bool HasConsoleCommandPermission(string command)
|
|
{
|
|
if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) return false;
|
|
|
|
command = command.ToLowerInvariant();
|
|
return permittedConsoleCommands.Any(c => c.ToLowerInvariant() == command);
|
|
}
|
|
|
|
public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
|
{
|
|
base.Draw(spriteBatch);
|
|
|
|
if (fileReceiver != null && fileReceiver.ActiveTransfers.Count > 0)
|
|
{
|
|
Vector2 pos = new Vector2(GameMain.NetLobbyScreen.InfoFrame.Rect.X, GameMain.GraphicsHeight - 35);
|
|
|
|
GUI.DrawRectangle(spriteBatch, new Rectangle(
|
|
(int)pos.X,
|
|
(int)pos.Y,
|
|
fileReceiver.ActiveTransfers.Count * 210 + 10,
|
|
32),
|
|
Color.Black * 0.8f, true);
|
|
|
|
for (int i = 0; i < fileReceiver.ActiveTransfers.Count; i++)
|
|
{
|
|
var transfer = fileReceiver.ActiveTransfers[i];
|
|
|
|
GUI.DrawString(spriteBatch,
|
|
pos,
|
|
ToolBox.LimitString(TextManager.Get("DownloadingFile").Replace("[filename]", transfer.FileName), GUI.SmallFont, 200),
|
|
Color.White, null, 0, GUI.SmallFont);
|
|
GUI.DrawProgressBar(spriteBatch, new Vector2(pos.X, -pos.Y - 15), new Vector2(135, 15), transfer.Progress, Color.Green);
|
|
GUI.DrawString(spriteBatch, pos + new Vector2(5, 15),
|
|
MathUtils.GetBytesReadable((long)transfer.Received) + " / " + MathUtils.GetBytesReadable((long)transfer.FileSize),
|
|
Color.White, null, 0, GUI.SmallFont);
|
|
|
|
if (GUI.DrawButton(spriteBatch, new Rectangle((int)pos.X + 140, (int)pos.Y + 18, 60, 15), TextManager.Get("Cancel"), new Color(0.47f, 0.13f, 0.15f, 0.08f)))
|
|
{
|
|
CancelFileTransfer(transfer);
|
|
fileReceiver.StopTransfer(transfer);
|
|
}
|
|
|
|
pos.X += 210;
|
|
}
|
|
}
|
|
|
|
if (!GameMain.DebugDraw) return;
|
|
|
|
int width = 200, height = 300;
|
|
int x = GameMain.GraphicsWidth - width, y = (int)(GameMain.GraphicsHeight * 0.3f);
|
|
|
|
GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black * 0.7f, true);
|
|
GUI.Font.DrawString(spriteBatch, "Network statistics:", new Vector2(x + 10, y + 10), Color.White);
|
|
|
|
if (client.ServerConnection != null)
|
|
{
|
|
GUI.Font.DrawString(spriteBatch, "Ping: " + (int)(client.ServerConnection.AverageRoundtripTime * 1000.0f) + " ms", new Vector2(x + 10, y + 25), Color.White);
|
|
|
|
y += 15;
|
|
|
|
GUI.SmallFont.DrawString(spriteBatch, "Received bytes: " + client.Statistics.ReceivedBytes, new Vector2(x + 10, y + 45), Color.White);
|
|
GUI.SmallFont.DrawString(spriteBatch, "Received packets: " + client.Statistics.ReceivedPackets, new Vector2(x + 10, y + 60), Color.White);
|
|
|
|
GUI.SmallFont.DrawString(spriteBatch, "Sent bytes: " + client.Statistics.SentBytes, new Vector2(x + 10, y + 75), Color.White);
|
|
GUI.SmallFont.DrawString(spriteBatch, "Sent packets: " + client.Statistics.SentPackets, new Vector2(x + 10, y + 90), Color.White);
|
|
}
|
|
else
|
|
{
|
|
GUI.Font.DrawString(spriteBatch, "Disconnected", new Vector2(x + 10, y + 25), Color.White);
|
|
}
|
|
}
|
|
|
|
|
|
public override void Disconnect()
|
|
{
|
|
client.Shutdown("");
|
|
steamAuthTicket?.Cancel();
|
|
steamAuthTicket = null;
|
|
|
|
foreach (var fileTransfer in FileReceiver.ActiveTransfers)
|
|
{
|
|
fileTransfer.Dispose();
|
|
}
|
|
|
|
if (HasPermission(ClientPermissions.ServerLog))
|
|
{
|
|
ServerLog?.Save();
|
|
}
|
|
|
|
GameMain.NetworkMember = null;
|
|
}
|
|
|
|
public void WriteCharacterInfo(NetOutgoingMessage msg)
|
|
{
|
|
msg.Write(characterInfo == null);
|
|
if (characterInfo == null) return;
|
|
|
|
msg.Write(characterInfo.Gender == Gender.Male);
|
|
msg.Write((byte)characterInfo.Race);
|
|
msg.Write((byte)characterInfo.HeadSpriteId);
|
|
msg.Write((byte)characterInfo.HairIndex);
|
|
msg.Write((byte)characterInfo.BeardIndex);
|
|
msg.Write((byte)characterInfo.MoustacheIndex);
|
|
msg.Write((byte)characterInfo.FaceAttachmentIndex);
|
|
|
|
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
|
int count = Math.Min(jobPreferences.Count, 3);
|
|
msg.Write((byte)count);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
msg.Write(jobPreferences[i].Identifier);
|
|
}
|
|
}
|
|
|
|
public override bool SelectCrewCharacter(Character character, GUIComponent characterFrame)
|
|
{
|
|
if (character == null) return false;
|
|
|
|
if (character != myCharacter)
|
|
{
|
|
var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == character);
|
|
if (client == null) return false;
|
|
|
|
if (HasPermission(ClientPermissions.Ban))
|
|
{
|
|
var banButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.15f), characterFrame.RectTransform, Anchor.BottomRight),
|
|
TextManager.Get("Ban"))
|
|
{
|
|
UserData = character.Name,
|
|
OnClicked = GameMain.NetLobbyScreen.BanPlayer
|
|
};
|
|
}
|
|
if (HasPermission(ClientPermissions.Kick))
|
|
{
|
|
var kickButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.15f), characterFrame.RectTransform, Anchor.BottomLeft),
|
|
TextManager.Get("Kick"))
|
|
{
|
|
UserData = character.Name,
|
|
OnClicked = GameMain.NetLobbyScreen.KickPlayer
|
|
};
|
|
}
|
|
else if (Voting.AllowVoteKick)
|
|
{
|
|
var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.15f), characterFrame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.16f) },
|
|
TextManager.Get("VoteToKick"))
|
|
{
|
|
UserData = character,
|
|
OnClicked = VoteForKick
|
|
};
|
|
if (GameMain.NetworkMember.ConnectedClients != null)
|
|
{
|
|
kickVoteButton.Enabled = !client.HasKickVoteFromID(myID);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Vote(VoteType voteType, object data)
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
|
|
msg.Write((byte)ClientNetObject.VOTE);
|
|
Voting.ClientWrite(msg, voteType, data);
|
|
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public bool VoteForKick(GUIButton button, object userdata)
|
|
{
|
|
var votedClient = userdata is Client ? (Client)userdata : otherClients.Find(c => c.Character == userdata);
|
|
if (votedClient == null) return false;
|
|
|
|
votedClient.AddKickVote(new Client(name, ID));
|
|
Vote(VoteType.Kick, votedClient);
|
|
|
|
button.Enabled = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void KickPlayer(string kickedName, string reason)
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.Kick);
|
|
msg.Write(kickedName);
|
|
msg.Write(reason);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public override void BanPlayer(string kickedName, string reason, bool range = false, TimeSpan? duration = null)
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.Ban);
|
|
msg.Write(kickedName);
|
|
msg.Write(reason);
|
|
msg.Write(range);
|
|
msg.Write(duration.HasValue ? duration.Value.TotalSeconds : 0.0); //0 = permaban
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public override void UnbanPlayer(string playerName, string playerIP)
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.Unban);
|
|
msg.Write(string.IsNullOrEmpty(playerName) ? "" : playerName);
|
|
msg.Write(string.IsNullOrEmpty(playerIP) ? "" : playerIP);
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public void SendCampaignState()
|
|
{
|
|
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
|
|
if (campaign == null)
|
|
{
|
|
DebugConsole.ThrowError("Failed send campaign state to the server (no campaign active).\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.ManageCampaign);
|
|
campaign.ClientWrite(msg);
|
|
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public void SendConsoleCommand(string command)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(command))
|
|
{
|
|
DebugConsole.ThrowError("Cannot send an empty console command to the server!\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.ConsoleCommands);
|
|
msg.Write(command);
|
|
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
|
msg.Write(cursorWorldPos.X);
|
|
msg.Write(cursorWorldPos.Y);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell the server to select a submarine (permission required)
|
|
/// </summary>
|
|
public void RequestSelectSub(int subIndex)
|
|
{
|
|
if (!HasPermission(ClientPermissions.SelectSub)) return;
|
|
if (subIndex < 0 || subIndex >= GameMain.NetLobbyScreen.SubList.CountChildren)
|
|
{
|
|
DebugConsole.ThrowError("Submarine index out of bounds (" + subIndex + ")\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.SelectSub);
|
|
msg.Write((UInt16)subIndex);
|
|
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell the server to select a submarine (permission required)
|
|
/// </summary>
|
|
public void RequestSelectMode(int modeIndex)
|
|
{
|
|
if (!HasPermission(ClientPermissions.SelectMode)) return;
|
|
if (modeIndex < 0 || modeIndex >= GameMain.NetLobbyScreen.ModeList.CountChildren)
|
|
{
|
|
DebugConsole.ThrowError("Gamemode index out of bounds (" + modeIndex + ")\n" + Environment.StackTrace);
|
|
return;
|
|
}
|
|
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.SelectMode);
|
|
msg.Write((UInt16)modeIndex);
|
|
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tell the server to end the round (permission required)
|
|
/// </summary>
|
|
public void RequestRoundEnd()
|
|
{
|
|
NetOutgoingMessage msg = client.CreateMessage();
|
|
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
|
|
msg.Write((UInt16)ClientPermissions.EndRound);
|
|
|
|
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
|
|
public bool SpectateClicked(GUIButton button, object userData)
|
|
{
|
|
if (button != null) button.Enabled = false;
|
|
|
|
NetOutgoingMessage readyToStartMsg = client.CreateMessage();
|
|
readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME);
|
|
|
|
//assume we have the required sub files to start the round
|
|
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
|
|
readyToStartMsg.Write(true);
|
|
|
|
WriteCharacterInfo(readyToStartMsg);
|
|
|
|
client.SendMessage(readyToStartMsg, NetDeliveryMethod.ReliableUnordered);
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool SetReadyToStart(GUITickBox tickBox)
|
|
{
|
|
if (gameStarted)
|
|
{
|
|
tickBox.Visible = false;
|
|
return false;
|
|
}
|
|
|
|
Vote(VoteType.StartRound, tickBox.Selected);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool ToggleEndRoundVote(GUITickBox tickBox)
|
|
{
|
|
if (!gameStarted) return false;
|
|
|
|
if (!Voting.AllowEndVoting || !HasSpawned)
|
|
{
|
|
tickBox.Visible = false;
|
|
return false;
|
|
}
|
|
|
|
Vote(VoteType.EndRound, tickBox.Selected);
|
|
return false;
|
|
}
|
|
|
|
public void ReportError(ClientNetError error, UInt16 expectedID = 0, UInt16 eventID = 0, UInt16 entityID = 0)
|
|
{
|
|
NetOutgoingMessage outMsg = client.CreateMessage();
|
|
outMsg.Write((byte)ClientPacketHeader.ERROR);
|
|
outMsg.Write((byte)error);
|
|
outMsg.Write(Level.Loaded == null ? 0 : Level.Loaded.EqualityCheckVal);
|
|
switch (error)
|
|
{
|
|
case ClientNetError.MISSING_EVENT:
|
|
outMsg.Write(expectedID);
|
|
outMsg.Write(eventID);
|
|
break;
|
|
case ClientNetError.MISSING_ENTITY:
|
|
outMsg.Write(eventID);
|
|
outMsg.Write(entityID);
|
|
break;
|
|
}
|
|
client.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
|
|
}
|
|
}
|
|
}
|