Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
Joonas Rikkonen c2e8263927 f9e8100...ccacceb
commit ccacceb16a184f00ecd384eede64ca9c4fab08a0
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 14:05:59 2019 +0200

    NetEntityEventManager checks the length of the event data (and logs an error if it's too long) before checking if there's still room to keep writing events in the packet. Checking the available room first could lead to situations where an excessively large event can't fit to any packet, "soft-locking" the EventManager without any error messages.

commit 5ac8259372aa900adc724aa4da1fd81af41ca195
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:41:52 2019 +0200

    Don't display disabled limbs on sonar (i.e. severed limbs that have "faded out")

commit 5f84df73ad86be96f3678c450351b3905e7317a4
Merge: b981f1635 dc429d6c4
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:41:16 2019 +0200

    Merge branch 'dev' of https://github.com/Regalis11/Barotrauma-development into dev

commit b981f163575b2bfc9a83b9925c94eca19b9d4554
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:36:19 2019 +0200

    Multiplayer campaign fixes:
    - Server uses a different temp folder to decompress save/sub files into than the clients. Should fix files occasionally getting corrupted and exceptions when trying to read the files when hosting a server from the main executable.
    - Some additional debug logging.
    - Use the base names of the adjacent locations as level seeds (i.e. "Vorta" instead of "Vorta Outpost"). The levels should not change when the type (and full name) of the location changes.

commit 42c5d18df77fc7acd5873d8e25f20bdd31b1ed76
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:31:06 2019 +0200

    Don't transfer files through the network when sending them to the owner of the server (i.e. a client hosting directly from the main executable), but simply tell the client where the file is located.

commit dc429d6c450f4893fe29c51d3c830527e587a871
Author: Daniel Asteljoki <daniel.asteljoki@gmail.com>
Date:   Mon Mar 25 13:30:26 2019 +0200

    Added labels next to periscopes in Humpback and Dugong

commit 789f02a87a2917dd2ae378f136cbe8dd3236c60d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:29:29 2019 +0200

    If loading a submarine fails, wait a bit and retry up to 4 times. Fixes loading occasionally failing when running multiple instances of the game from the same directory.

commit be9ea3a58832992b6226917117247e1bf1efeff9
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 11:03:36 2019 +0200

    Fixed a bunch of disconnection messages being in an incorrect format & DisconnectUnauthClient not getting the messages from the xml

commit c6f744b4d6b3520720010f5cd4f22a25b42bfc8b
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 10:43:10 2019 +0200

    Log entity event errors into server logs when verbose logging is enabled
2019-03-25 14:30:00 +02:00

2464 lines
102 KiB
C#

using Barotrauma.Items.Components;
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;
using System.Threading;
namespace Barotrauma.Networking
{
class GameClient : NetworkMember
{
public override bool IsClient
{
get { return true; }
}
private NetClient client;
private GUIMessageBox reconnectBox, waitInServerQueueBox;
//TODO: move these to NetLobbyScreen
public GUIButton EndRoundButton;
public GUITickBox EndVoteTickBox;
private NetStats netStats;
protected GUITickBox cameraFollowsSub;
private ClientPermissions permissions = ClientPermissions.None;
private List<string> permittedConsoleCommands = new List<string>();
private bool connected;
private byte myID;
private List<Client> otherClients;
private readonly List<Submarine> serverSubmarines = new List<Submarine>();
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 VoipClient VoipClient
{
get;
private set;
}
public override List<Client> ConnectedClients
{
get
{
return otherClients;
}
}
public FileReceiver FileReceiver
{
get { return fileReceiver; }
}
public bool MidRoundSyncing
{
get { return entityEventManager.MidRoundSyncing; }
}
private int ownerKey;
public bool IsServerOwner
{
get { return ownerKey > 0; }
}
public GameClient(string newName, string ip, int ownerKey=0)
{
//TODO: gui stuff should probably not be here?
this.ownerKey = ownerKey;
netStats = new NetStats();
inGameHUD = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
{
CanBeFocused = false
};
cameraFollowsSub = new GUITickBox(new RectTransform(new Vector2(0.05f, 0.05f), inGameHUD.RectTransform, anchor: Anchor.TopCenter)
{
AbsoluteOffset = new Point(0, 5),
MaxSize = new Point(25, 25)
}, TextManager.Get("CamFollowSubmarine"))
{
Selected = Camera.FollowSub,
OnSelected = (tbox) =>
{
Camera.FollowSub = tbox.Selected;
return true;
}
};
chatBox = new ChatBox(inGameHUD, isSinglePlayer: false);
chatBox.OnEnterMessage += EnterChatMessage;
chatBox.InputBox.OnTextChanged += TypingChatMessage;
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = 5,
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.ManageRound)) return false;
RequestRoundEnd();
return true;
},
Visible = false
};
EndVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.4f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
TextManager.Get("EndRound"))
{
UserData = 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 (serverSettings.ServerLog.LogFrame == null)
{
serverSettings.ServerLog.CreateLogFrame();
}
else
{
serverSettings.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, null)
{
Job = null
};
otherClients = new List<Client>();
serverSettings = new ServerSettings("Server", 0, 0, 0, false, false);
ConnectToServer(ip);
//ServerLog = new ServerLog("");
ChatMessage.LastID = 0;
GameMain.NetLobbyScreen = new NetLobbyScreen();
}
private void ConnectToServer(string hostIP)
{
chatBox.InputBox.Enabled = false;
if (GameMain.NetLobbyScreen?.TextBox != null)
{
GameMain.NetLobbyScreen.TextBox.Enabled = false;
}
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();
chatBox.InputBox.Enabled = true;
if (GameMain.NetLobbyScreen?.TextBox != null)
{
GameMain.NetLobbyScreen.TextBox.Enabled = true;
}
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(TextManager.Get("Disconnecting"));
ConnectToServer(serverIP);
return true;
}
private bool ReturnToServerList(GUIButton button, object obj)
{
Disconnect();
Submarine.Unload();
GameMain.Client = 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("Connecting");
while (!CanStart && !connectCancelled)
{
if (reconnectBox == null)
{
reconnectBox = new GUIMessageBox(connectingText, TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP), new string[] { TextManager.Get("Cancel") });
reconnectBox.Buttons[0].OnClicked += CancelConnect;
reconnectBox.Buttons[0].OnClicked += reconnectBox.Close;
}
reconnectBox.Header.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 = TextManager.Get("PasswordRequired");
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;
chatBox.InputBox.Enabled = true;
if (GameMain.NetLobbyScreen?.TextBox != null)
{
GameMain.NetLobbyScreen.TextBox.Enabled = true;
}
}
yield return CoroutineStatus.Success;
}
private void WriteAuthRequest(NetOutgoingMessage outmsg)
{
if (SteamManager.IsInitialized && SteamManager.USE_STEAM)
{
outmsg.Write((byte)ClientPacketHeader.REQUEST_STEAMAUTH);
}
else
{
DebugConsole.Log("Sending auth request");
outmsg.Write((byte)ClientPacketHeader.REQUEST_AUTH);
}
outmsg.Write(ownerKey);
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(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);
}
}
public override void Update(float deltaTime)
{
#if DEBUG
if (PlayerInput.GetKeyboardState.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.P)) return;
#endif
foreach (Client c in ConnectedClients)
{
c.UpdateSoundPosition();
}
if (VoipCapture.Instance != null)
{
if (VoipCapture.Instance.LastEnqueueAudio > DateTime.Now - new TimeSpan(0, 0, 0, 0, milliseconds: 100))
{
var myClient = ConnectedClients.Find(c => c.ID == ID);
if (Screen.Selected == GameMain.NetLobbyScreen)
{
GameMain.NetLobbyScreen.SetPlayerSpeaking(myClient);
}
else
{
GameMain.GameSession?.CrewManager?.SetPlayerSpeaking(myClient);
}
}
}
if (gameStarted) SetRadioButtonColor();
if (ShowNetStats && client?.ServerConnection != null)
{
netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes);
netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes);
netStats.AddValue(NetStats.NetStatType.ResentMessages, client.ServerConnection.Statistics.ResentMessages);
netStats.Update(deltaTime);
}
UpdateHUD(deltaTime);
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"), TextManager.Get("MessageReadError").Replace("[message]", e.Message).Replace("[targetsite]", e.TargetSite.ToString()));
Disconnect();
GameMain.MainMenuScreen.Select();
return;
}
if (gameStarted && Screen.Selected == GameMain.GameScreen)
{
EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned;
if (respawnManager != null)
{
respawnManager.Update(deltaTime);
}
if (updateTimer > DateTime.Now) { return; }
SendIngameUpdate();
}
else
{
if (updateTimer > DateTime.Now) { return; }
SendLobbyUpdate();
}
if (serverSettings.VoiceChatEnabled)
{
VoipClient?.SendToServer();
}
// 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.VOICE:
VoipClient.Read(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();
Character.TeamType winningTeam = (Character.TeamType)inc.ReadByte();
if (missionSuccessful && GameMain.GameSession?.Mission != null)
{
GameMain.GameSession.WinningTeam = winningTeam;
GameMain.GameSession.Mission.Completed = true;
}
CoroutineManager.StartCoroutine(EndGame(endMessage));
break;
case ServerPacketHeader.CAMPAIGN_SETUP_INFO:
UInt16 saveCount = inc.ReadUInt16();
List<string> saveFiles = new List<string>();
for (int i = 0; i < saveCount; i++)
{
saveFiles.Add(inc.ReadString());
}
GameMain.NetLobbyScreen.CampaignSetupUI = MultiPlayerCampaign.StartCampaignSetup(serverSubmarines, saveFiles);
break;
case ServerPacketHeader.PERMISSIONS:
ReadPermissions(inc);
break;
case ServerPacketHeader.ACHIEVEMENT:
ReadAchievement(inc);
break;
case ServerPacketHeader.CHEATS_ENABLED:
bool cheatsEnabled = inc.ReadBoolean();
inc.ReadPadBits();
if (cheatsEnabled == DebugConsole.CheatsEnabled)
{
continue;
}
else
{
DebugConsole.CheatsEnabled = cheatsEnabled;
SteamAchievementManager.CheatsEnabled = cheatsEnabled;
if (cheatsEnabled)
{
new GUIMessageBox(TextManager.Get("CheatsEnabledTitle"), TextManager.Get("CheatsEnabledDescription"));
}
}
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);
DebugConsole.NewMessage("Received a disconnect message (" + disconnectMsg + ")");
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)
{
DebugConsole.NewMessage("Attempting to reconnect...");
string msg = TextManager.GetServerMessage(disconnectMsg);
msg = string.IsNullOrWhiteSpace(msg) ?
TextManager.Get("ConnectionLostReconnecting") :
msg + '\n' + TextManager.Get("ConnectionLostReconnecting");
reconnectBox = new GUIMessageBox(
TextManager.Get("ConnectionLost"),
msg, new string[0]);
connected = false;
ConnectToServer(serverIP);
}
else
{
string msg = "";
if (disconnectReason == DisconnectReason.Unknown)
{
DebugConsole.NewMessage("Do not attempt reconnect (not allowed).");
msg = disconnectMsg;
}
else
{
DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection).");
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
for (int i = 1; i < splitMsg.Length; i++)
{
msg += TextManager.GetServerMessage(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>();
byte clientID = inc.ReadByte();
ClientPermissions permissions = ClientPermissions.None;
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
Client.ReadPermissions(inc, out permissions, out permittedCommands);
Client targetClient = ConnectedClients.Find(c => c.ID == clientID);
if (targetClient != null)
{
targetClient.SetPermissions(permissions, permittedCommands);
}
if (clientID == myID)
{
SetMyPermissions(permissions, permittedCommands.Select(command => command.names[0]));
}
}
private void SetMyPermissions(ClientPermissions newPermissions, IEnumerable<string> permittedConsoleCommands)
{
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
{
if (newPermissions == permissions) return;
}
permissions = newPermissions;
this.permittedConsoleCommands = new List<string>(permittedConsoleCommands);
//don't show the "permissions changed" popup if the client owns the server
if (ownerKey == 0)
{
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";
}
}
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.15f) },
TextManager.Get("PermittedConsoleCommands"), wrap: true, font: GUI.SmallFont);
var commandList = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.55f), msgBox.InnerFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.05f, 0.25f) });
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.UpdatePermissions();
}
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();
int levelEqualityCheckVal = inc.ReadInt32();
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;
serverSettings.ReadMonsterEnabled(inc);
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;
serverSettings.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 (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
{
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
DebugConsole.ThrowError(errorMsg, createMessageBox: true);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
CoroutineManager.StartCoroutine(EndGame(""));
yield return CoroutineStatus.Failure;
}
if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null);
gameStarted = true;
GameMain.GameScreen.Select();
AddChatMessage($"ServerMessage.HowToCommunicate~[chatbutton]={GameMain.Config.KeyBind(InputType.Chat).ToString()}~[radiobutton]={GameMain.Config.KeyBind(InputType.RadioChat).ToString()}", ChatMessageType.Server);
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();
VoipClient = new VoipClient(this, client);
UInt16 subListCount = inc.ReadUInt16();
serverSubmarines.Clear();
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)
{
serverSubmarines.Add(matchingSub);
}
else
{
serverSubmarines.Add(new Submarine(Path.Combine(Submarine.SavePath, subName) + ".sub", subHash, false));
}
}
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, serverSubmarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, serverSubmarines);
gameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
ReadPermissions(inc);
if (gameStarted)
{
new GUIMessageBox(TextManager.Get("PleaseWait"), TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled"));
GameMain.NetLobbyScreen.Select();
}
}
private void ReadClientList(NetIncomingMessage inc)
{
UInt16 listId = inc.ReadUInt16();
List<TempClient> tempClients = new List<TempClient>();
int clientCount = inc.ReadByte();
for (int i = 0; i < clientCount; i++)
{
byte id = inc.ReadByte();
string name = inc.ReadString();
UInt16 characterID = inc.ReadUInt16();
bool muted = inc.ReadBoolean();
inc.ReadPadBits();
tempClients.Add(new TempClient
{
ID = id,
Name = name,
CharacterID = characterID,
Muted = muted
});
}
if (NetIdUtils.IdMoreRecent(listId, LastClientListUpdateID))
{
bool updateClientListId = true;
List<Client> currentClients = new List<Client>();
foreach (TempClient tc in tempClients)
{
//see if the client already exists
var existingClient = ConnectedClients.Find(c => c.ID == tc.ID && c.Name == tc.Name);
if (existingClient == null) //if not, create it
{
existingClient = new Client(tc.Name, tc.ID)
{
Muted = tc.Muted
};
ConnectedClients.Add(existingClient);
GameMain.NetLobbyScreen.AddPlayer(existingClient);
}
existingClient.Character = null;
existingClient.Muted = tc.Muted;
if (tc.CharacterID > 0)
{
existingClient.Character = Entity.FindEntityByID(tc.CharacterID) as Character;
if (existingClient.Character == null)
{
updateClientListId = false;
}
}
if (existingClient.ID == myID)
{
existingClient.SetPermissions(permissions, permittedConsoleCommands);
}
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]);
ConnectedClients[i].Dispose();
ConnectedClients.RemoveAt(i);
}
}
if (updateClientListId) LastClientListUpdateID = listId;
}
}
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();
UInt16 settingsLen = inc.ReadUInt16();
byte[] settingsData = inc.ReadBytes(settingsLen);
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 voiceChatEnabled = 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;
//ignore the message if we already a more up-to-date one
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID))
{
NetBuffer settingsBuf = new NetBuffer();
settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.Position = 0;
serverSettings.ClientRead(settingsBuf);
GameMain.NetLobbyScreen.LastUpdateID = updateID;
serverSettings.ServerLog.ServerName = serverSettings.ServerName;
if (!GameMain.NetLobbyScreen.ServerName.Selected) GameMain.NetLobbyScreen.ServerName.Text = serverSettings.ServerName;
if (!GameMain.NetLobbyScreen.ServerMessage.Selected) GameMain.NetLobbyScreen.ServerMessage.Text = serverSettings.ServerMessageText;
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);
serverSettings.VoiceChatEnabled = voiceChatEnabled;
serverSettings.Voting.AllowSubVoting = allowSubVoting;
serverSettings.Voting.AllowModeVoting = allowModeVoting;
GUI.KeyboardDispatcher.Subscriber = prevDispatcher;
}
}
bool campaignUpdated = inc.ReadBoolean();
inc.ReadPadBits();
if (campaignUpdated)
{
MultiPlayerCampaign.ClientRead(inc);
}
lastSentChatMsgID = inc.ReadUInt16();
break;
case ServerNetObject.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
break;
case ServerNetObject.VOTE:
serverSettings.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.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.ENTITY_EVENT:
case ServerNetObject.ENTITY_EVENT_INITIAL:
if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) { break; }
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);
outmsg.Write(LastClientListUpdateID);
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);
outmsg.Write(LastClientListUpdateID);
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;
DebugConsole.Log("Campaign save received, save ID " + campaign.LastSaveID);
//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 override void CreateEntityEvent(INetSerializable entity, object[] extraData)
{
if (!(entity is IClientSerializable)) throw new InvalidCastException("Entity is not IClientSerializable");
entityEventManager.CreateEvent(entity as IClientSerializable, extraData);
}
public bool HasPermission(ClientPermissions permission)
{
return permissions.HasFlag(permission);
}
public bool HasConsoleCommandPermission(string commandName)
{
if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; }
commandName = commandName.ToLowerInvariant();
if (permittedConsoleCommands.Any(c => c.ToLowerInvariant() == commandName)) { return true; }
//check aliases
foreach (DebugConsole.Command command in DebugConsole.Commands)
{
if (command.names.Contains(commandName))
{
if (command.names.Intersect(permittedConsoleCommands).Any()) { return true; }
break;
}
}
return false;
}
public override void Disconnect()
{
client.Shutdown("");
steamAuthTicket?.Cancel();
steamAuthTicket = null;
foreach (var fileTransfer in FileReceiver.ActiveTransfers)
{
fileTransfer.Dispose();
}
if (HasPermission(ClientPermissions.ServerLog))
{
serverSettings.ServerLog?.Save();
}
if (GameMain.ServerChildProcess != null)
{
int checks = 0;
while (!GameMain.ServerChildProcess.HasExited) {
if (checks > 10)
{
GameMain.ServerChildProcess.Kill();
}
Thread.Sleep(100);
checks++;
}
GameMain.ServerChildProcess = null;
}
VoipClient?.Dispose();
VoipClient = null;
GameMain.Client = null;
}
public void WriteCharacterInfo(NetOutgoingMessage msg)
{
msg.Write(characterInfo == null);
if (characterInfo == null) return;
msg.Write((byte)characterInfo.Gender);
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 void Vote(VoteType voteType, object data)
{
NetOutgoingMessage msg = client.CreateMessage();
msg.Write((byte)ClientPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ClientNetObject.VOTE);
serverSettings.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(ConnectedClients.First(c => c.ID == ID));
Vote(VoteType.Kick, votedClient);
button.Enabled = false;
return true;
}
public override void AddChatMessage(ChatMessage message)
{
base.AddChatMessage(message);
if (string.IsNullOrEmpty(message.Text)) { return; }
GameMain.NetLobbyScreen.NewChatMessage(message);
chatBox.AddMessage(message);
}
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 UpdateClientPermissions(Client targetClient)
{
NetOutgoingMessage msg = client.CreateMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManagePermissions);
targetClient.WritePermissions(msg);
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 start the round (permission required)
/// </summary>
public void RequestStartRound()
{
if (!HasPermission(ClientPermissions.ManageRound)) return;
NetOutgoingMessage msg = client.CreateMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManageRound);
msg.Write(false); //indicates round start
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
}
/// <summary>
/// Tell the server to select a submarine (permission required)
/// </summary>
public void RequestSelectSub(int subIndex, bool isShuttle)
{
if (!HasPermission(ClientPermissions.SelectSub)) return;
var subList = isShuttle ? GameMain.NetLobbyScreen.ShuttleList.ListBox : GameMain.NetLobbyScreen.SubList;
if (subIndex < 0 || subIndex >= subList.Content.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(isShuttle); msg.WritePadBits();
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.Content.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);
}
public void SetupNewCampaign(Submarine sub, string savePath, string mapSeed)
{
NetOutgoingMessage msg = client.CreateMessage();
msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.Write(true); msg.WritePadBits();
msg.Write(savePath);
msg.Write(mapSeed);
msg.Write(sub.Name);
msg.Write(sub.MD5Hash.Hash);
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
GameMain.NetLobbyScreen.CampaignSetupUI = null;
}
public void SetupLoadCampaign(string saveName)
{
NetOutgoingMessage msg = client.CreateMessage();
msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO);
msg.Write(false); msg.WritePadBits();
msg.Write(saveName);
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
GameMain.NetLobbyScreen.CampaignSetupUI = null;
}
/// <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.ManageRound);
msg.Write(true); //indicates round end
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 (!serverSettings.Voting.AllowEndVoting || !HasSpawned)
{
tickBox.Visible = false;
return false;
}
Vote(VoteType.EndRound, tickBox.Selected);
return false;
}
protected CharacterInfo characterInfo;
protected Character myCharacter;
public CharacterInfo CharacterInfo
{
get { return characterInfo; }
set { characterInfo = value; }
}
public Character Character
{
get { return myCharacter; }
set { myCharacter = value; }
}
protected GUIFrame inGameHUD;
protected ChatBox chatBox;
public GUIButton ShowLogButton; //TODO: move to NetLobbyScreen
private float myCharacterFrameOpenState;
public GUIFrame InGameHUD
{
get { return inGameHUD; }
}
public ChatBox ChatBox
{
get { return chatBox; }
}
protected void SetRadioButtonColor()
{
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f)
{
chatBox.RadioButton.GetChild<GUIImage>().Color = new Color(60, 60, 60, 255);
}
else
{
var radioItem = Character.Controlled?.Inventory?.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
chatBox.RadioButton.GetChild<GUIImage>().Color =
(radioItem != null && Character.Controlled.HasEquippedItem(radioItem) && radioItem.GetComponent<WifiComponent>().CanTransmit()) ?
Color.White : new Color(60, 60, 60, 255);
}
}
public bool TypingChatMessage(GUITextBox textBox, string text)
{
return chatBox.TypingChatMessage(textBox, text);
}
public bool EnterChatMessage(GUITextBox textBox, string message)
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
if (string.IsNullOrWhiteSpace(message))
{
if (textBox == chatBox.InputBox) textBox.Deselect();
return false;
}
SendChatMessage(message);
textBox.Deselect();
textBox.Text = "";
return true;
}
public virtual void AddToGUIUpdateList()
{
if (GUI.DisableHUD || GUI.DisableUpperHUD) return;
if (gameStarted &&
Screen.Selected == GameMain.GameScreen)
{
inGameHUD.AddToGUIUpdateList();
if (Character.Controlled == null)
{
GameMain.NetLobbyScreen.MyCharacterFrame.AddToGUIUpdateList();
}
}
}
public void UpdateHUD(float deltaTime)
{
GUITextBox msgBox = null;
if (Screen.Selected == GameMain.GameScreen)
{
msgBox = chatBox.InputBox;
}
else if (Screen.Selected == GameMain.NetLobbyScreen)
{
msgBox = GameMain.NetLobbyScreen.TextBox;
}
if (gameStarted && Screen.Selected == GameMain.GameScreen)
{
if (!GUI.DisableHUD && !GUI.DisableUpperHUD)
{
inGameHUD.UpdateManually(deltaTime);
chatBox.Update(deltaTime);
cameraFollowsSub.Visible = Character.Controlled == null;
if (Character.Controlled == null)
{
myCharacterFrameOpenState = GameMain.NetLobbyScreen.MyCharacterFrameOpen ? myCharacterFrameOpenState + deltaTime * 5 : myCharacterFrameOpenState - deltaTime * 5;
myCharacterFrameOpenState = MathHelper.Clamp(myCharacterFrameOpenState, 0.0f, 1.0f);
var myCharFrame = GameMain.NetLobbyScreen.MyCharacterFrame;
int padding = GameMain.GraphicsWidth - myCharFrame.Parent.Rect.Right;
myCharFrame.RectTransform.AbsoluteOffset =
Vector2.SmoothStep(new Vector2(-myCharFrame.Rect.Width - padding, 0.0f), new Vector2(-padding, 0), myCharacterFrameOpenState).ToPoint();
}
}
if (Character.Controlled == null || Character.Controlled.IsDead)
{
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false;
}
}
//tab doesn't autoselect the chatbox when debug console is open,
//because tab is used for autocompleting console commands
if (msgBox != null)
{
if ((PlayerInput.KeyHit(InputType.Chat) || PlayerInput.KeyHit(InputType.RadioChat)) &&
GUI.KeyboardDispatcher.Subscriber == null)
{
if (msgBox.Selected)
{
msgBox.Text = "";
msgBox.Deselect();
}
else
{
msgBox.Select();
if (Screen.Selected == GameMain.GameScreen && PlayerInput.KeyHit(InputType.RadioChat))
{
msgBox.Text = "r; ";
}
}
}
}
serverSettings.AddToGUIUpdateList();
if (serverSettings.ServerLog.LogFrame != null) serverSettings.ServerLog.LogFrame.AddToGUIUpdateList();
}
public virtual void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{
if (!gameStarted || Screen.Selected != GameMain.GameScreen || GUI.DisableHUD || GUI.DisableUpperHUD) return;
inGameHUD.DrawManually(spriteBatch);
if (EndVoteCount > 0)
{
if (EndVoteTickBox.Visible)
{
EndVoteTickBox.Text =
(EndVoteTickBox.UserData as string) + " " + EndVoteCount + "/" + EndVoteMax;
}
else
{
string endVoteText = TextManager.Get("EndRoundVotes")
.Replace("[votes]", EndVoteCount.ToString())
.Replace("[max]", EndVoteMax.ToString());
GUI.DrawString(spriteBatch, EndVoteTickBox.Rect.Center.ToVector2() - GUI.SmallFont.MeasureString(endVoteText) / 2,
endVoteText,
Color.White,
font: GUI.SmallFont);
}
}
else
{
EndVoteTickBox.Text = EndVoteTickBox.UserData as string;
}
if (respawnManager != null)
{
string respawnInfo = "";
if (respawnManager.CurrentState == RespawnManager.State.Waiting &&
respawnManager.CountdownStarted)
{
respawnInfo = TextManager.Get(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn");
respawnInfo = respawnInfo.Replace("[time]", ToolBox.SecondsToReadableTime(respawnManager.RespawnTimer));
}
else if (respawnManager.CurrentState == RespawnManager.State.Transporting)
{
respawnInfo = respawnManager.TransportTimer <= 0.0f ?
"" :
TextManager.Get("RespawnShuttleLeavingIn").Replace("[time]", ToolBox.SecondsToReadableTime(respawnManager.TransportTimer));
}
if (respawnManager != null)
{
GUI.DrawString(spriteBatch,
new Vector2(120.0f, 10),
respawnInfo, Color.White, null, 0, GUI.SmallFont);
}
}
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 (!ShowNetStats) return;
netStats.Draw(spriteBatch, new Rectangle(300, 10, 300, 150));
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 virtual 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 (serverSettings.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 CreateKickReasonPrompt(string clientName, bool ban, bool rangeBan = false)
{
var banReasonPrompt = new GUIMessageBox(
TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"),
"", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, 400, 300);
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center));
var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform))
{
Wrap = true,
MaxTextLength = 100
};
GUINumberInput durationInputDays = null, durationInputHours = null;
GUITickBox permaBanTickBox = null;
if (ban)
{
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform), TextManager.Get("BanDuration"));
permaBanTickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) },
TextManager.Get("BanPermanent"))
{
Selected = true
};
var durationContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform), isHorizontal: true)
{
Visible = false
};
permaBanTickBox.OnSelected += (tickBox) =>
{
durationContainer.Visible = !tickBox.Selected;
return true;
};
durationInputDays = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueFloat = 1000
};
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), TextManager.Get("Days"));
durationInputHours = new GUINumberInput(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = 0,
MaxValueFloat = 24
};
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), durationContainer.RectTransform), TextManager.Get("Hours"));
}
banReasonPrompt.Buttons[0].OnClicked += (btn, userData) =>
{
if (ban)
{
if (!permaBanTickBox.Selected)
{
TimeSpan banDuration = new TimeSpan(durationInputDays.IntValue, durationInputHours.IntValue, 0, 0);
BanPlayer(clientName, banReasonBox.Text, ban, banDuration);
}
else
{
BanPlayer(clientName, banReasonBox.Text, ban);
}
}
else
{
KickPlayer(clientName, banReasonBox.Text);
}
return true;
};
banReasonPrompt.Buttons[0].OnClicked += banReasonPrompt.Close;
banReasonPrompt.Buttons[1].OnClicked += banReasonPrompt.Close;
}
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);
}
}
}