Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
T
Joonas Rikkonen 74086415fc 1ec6577...e08c5e7
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
2019-03-18 21:12:02 +02:00

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);
}
}
}