diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index 3fe2f6aa7..308dbada2 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -136,19 +136,38 @@ namespace Barotrauma frame.Draw(spriteBatch); } + private static bool IsCommandPermitted(string command, GameClient client) + { + switch (command) + { + case "kick": + return client.HasPermission(ClientPermissions.Kick); + case "ban": + case "banip": + return client.HasPermission(ClientPermissions.Ban); + case "netstats": + case "help": + case "dumpids": + return true; + default: + return false; + } + } + public static void ExecuteCommand(string command, GameMain game) { + if (string.IsNullOrWhiteSpace(command)) return; + string[] commands = command.Split(' '); + + #if !DEBUG - if (GameMain.Client != null) + if (GameMain.Client != null && !IsCommandPermitted(commands[0].ToLowerInvariant(), GameMain.Client)) { - ThrowError("Console commands are disabled in multiplayer mode"); + ThrowError("You're not permitted to use the command ''" + commands[0].ToLowerInvariant()+"''!"); return; } #endif - if (command == "") return; - string[] commands = command.Split(' '); - switch (commands[0].ToLowerInvariant()) { case "help": @@ -189,7 +208,6 @@ namespace Barotrauma NewMessage(" ", Color.Cyan); - NewMessage("kick [name]: kick a player out from the server", Color.Cyan); NewMessage("ban [name]: kick and ban the player from the server", Color.Cyan); NewMessage("banip [IP address]: ban the IP address from the server", Color.Cyan); @@ -284,13 +302,14 @@ namespace Barotrauma HumanAIController.DisableCrewAI = false; break; case "kick": - if (GameMain.Server == null || commands.Length < 2) break; - GameMain.Server.KickPlayer(string.Join(" ", commands.Skip(1))); + if (GameMain.NetworkMember == null || commands.Length < 2) break; + GameMain.NetworkMember.KickPlayer(string.Join(" ", commands.Skip(1)), false); break; case "ban": - if (GameMain.Server == null || commands.Length < 2) break; - GameMain.Server.KickPlayer(string.Join(" ", commands.Skip(1)), true); + if (GameMain.NetworkMember == null || commands.Length < 2) break; + GameMain.NetworkMember.KickPlayer(string.Join(" ", commands.Skip(1)), true); + break; case "banip": if (GameMain.Server == null || commands.Length < 2) break; @@ -601,6 +620,7 @@ namespace Barotrauma DebugConsole.NewMessage("Deleted filelist", Color.Green); } + if (System.IO.File.Exists("Submarines/TutorialSub.sub")) { System.IO.File.Delete("Submarines/TutorialSub.sub"); @@ -614,6 +634,13 @@ namespace Barotrauma DebugConsole.NewMessage("Deleted server settings", Color.Green); } + if (System.IO.File.Exists(GameServer.ClientPermissionsFile)) + { + System.IO.File.Delete(GameServer.ClientPermissionsFile); + DebugConsole.NewMessage("Deleted client permission file", Color.Green); + + } + if (System.IO.File.Exists("crashreport.txt")) { System.IO.File.Delete("crashreport.txt"); diff --git a/Subsurface/Source/GUI/GUITickBox.cs b/Subsurface/Source/GUI/GUITickBox.cs index d78c90f1a..e65d78c2a 100644 --- a/Subsurface/Source/GUI/GUITickBox.cs +++ b/Subsurface/Source/GUI/GUITickBox.cs @@ -41,6 +41,11 @@ namespace Barotrauma } public GUITickBox(Rectangle rect, string label, Alignment alignment, GUIComponent parent) + : this(rect, label, alignment, GUI.Font, parent) + { + } + + public GUITickBox(Rectangle rect, string label, Alignment alignment, SpriteFont font, GUIComponent parent) : base(null) { if (parent != null) @@ -50,7 +55,7 @@ namespace Barotrauma box.HoverColor = Color.Gray; box.SelectedColor = Color.DarkGray; - text = new GUITextBlock(new Rectangle(rect.X + 30, rect.Y+2, 20, rect.Height), label, Color.Transparent, Color.White, Alignment.TopLeft, null, this); + text = new GUITextBlock(new Rectangle(rect.Right + 10, rect.Y+2, 20, rect.Height), label, GUI.Style, this, font); this.rect = new Rectangle(box.Rect.X, box.Rect.Y, 240, rect.Height); diff --git a/Subsurface/Source/GameSession/GameSession.cs b/Subsurface/Source/GameSession/GameSession.cs index ba2eac0b6..356b69974 100644 --- a/Subsurface/Source/GameSession/GameSession.cs +++ b/Subsurface/Source/GameSession/GameSession.cs @@ -166,7 +166,7 @@ namespace Barotrauma if (GameMain.Server != null) { - CoroutineManager.StartCoroutine(GameMain.Server.EndGame(endMessage)); + //CoroutineManager.StartCoroutine(GameMain.Server.EndGame(endMessage)); } else if (GameMain.Client == null) diff --git a/Subsurface/Source/Networking/BanList.cs b/Subsurface/Source/Networking/BanList.cs index 3908ba0bf..f9960dbd1 100644 --- a/Subsurface/Source/Networking/BanList.cs +++ b/Subsurface/Source/Networking/BanList.cs @@ -79,9 +79,6 @@ namespace Barotrauma.Networking public GUIComponent CreateBanFrame(GUIComponent parent) { - //GUIFrame banFrame = new GUIFrame(new Rectangle(0,0,0,0), null, Alignment.Center, GUI.Style, parent); - - //new GUITextBlock(new Rectangle(0, -10, 0, 30), "Banned IPs:", GUI.Style, Alignment.Left, Alignment.Left, banFrame, false, GUI.LargeFont); banFrame = new GUIListBox(new Rectangle(0, 0, 0, 0), GUI.Style, parent); foreach (BannedPlayer bannedPlayer in bannedPlayers) @@ -91,18 +88,14 @@ namespace Barotrauma.Networking bannedPlayer.IP + " (" + bannedPlayer.Name + ")", GUI.Style, Alignment.Left, Alignment.Left, banFrame); - textBlock.Padding = new Vector4(10.0f, 0.0f, 0.0f, 0.0f); + textBlock.Padding = new Vector4(10.0f, 10.0f, 0.0f, 0.0f); textBlock.UserData = banFrame; - var removeButton = new GUIButton(new Rectangle(0, 00, 100, 20), "Remove", Alignment.Right | Alignment.CenterY, GUI.Style, textBlock); + var removeButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Remove", Alignment.Right | Alignment.CenterY, GUI.Style, textBlock); removeButton.UserData = bannedPlayer; removeButton.OnClicked = RemoveBan; } - //var closeButton = new GUIButton(new Rectangle(0,30,100,20), "Close", Alignment.BottomRight, GUI.Style, banFrame); - //closeButton.OnClicked = CloseFrame; - - return banFrame; } diff --git a/Subsurface/Source/Networking/Client.cs b/Subsurface/Source/Networking/Client.cs new file mode 100644 index 000000000..55c8cfc3a --- /dev/null +++ b/Subsurface/Source/Networking/Client.cs @@ -0,0 +1,169 @@ +using Barotrauma.Networking.ReliableMessages; +using Lidgren.Network; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace Barotrauma.Networking +{ + [Flags] + enum ClientPermissions + { + None = 0, + [Description("End round")] + EndRound = 1, + [Description("Kick")] + Kick = 2, + [Description("Ban")] + Ban = 4 + } + + class Client + { + public string name; + public byte ID; + + public Character Character; + public CharacterInfo characterInfo; + public NetConnection Connection { get; set; } + public string version; + public bool inGame; + + private List kickVoters; + + public bool ReadyToStart; + + private object[] votes; + + public List jobPreferences; + public JobPrefab assignedJob; + + public FileStreamSender FileStreamSender; + + public ReliableChannel ReliableChannel; + + public float deleteDisconnectedTimer; + + public ClientPermissions Permissions; + + public int KickVoteCount + { + get { return kickVoters.Count; } + } + + public Client(NetPeer server, string name, byte ID) + : this(name, ID) + { + ReliableChannel = new ReliableChannel(server); + } + + public Client(string name, byte ID) + { + this.name = name; + this.ID = ID; + + kickVoters = new List(); + + votes = new object[Enum.GetNames(typeof(VoteType)).Length]; + + jobPreferences = new List(JobPrefab.List.GetRange(0, 3)); + } + + public static bool IsValidName(string name) + { + if (name.Contains("\n") || name.Contains("\r\n")) return false; + + return (name.All(c => + c != ';' && + c != ',' && + c != '<' && + c != '/')); + } + + public static string SanitizeName(string name) + { + if (name.Length > 20) + { + name = name.Substring(0, 20); + } + + return name; + } + + public void SetPermissions(ClientPermissions permissions) + { + this.Permissions = permissions; + } + + public void GivePermission(ClientPermissions permission) + { + this.Permissions |= permission; + } + + public void RemovePermission(ClientPermissions permission) + { + this.Permissions &= ~permission; + } + + public bool HasPermission(ClientPermissions permission) + { + return Permissions.HasFlag(permission); + } + + public T GetVote(VoteType voteType) + { + return (votes[(int)voteType] is T) ? (T)votes[(int)voteType] : default(T); + } + + public void SetVote(VoteType voteType, object value) + { + votes[(int)voteType] = value; + } + + public void ResetVotes() + { + for (int i = 0; i < votes.Length; i++) + { + votes[i] = null; + } + } + + public void AddKickVote(Client voter) + { + if (!kickVoters.Contains(voter)) kickVoters.Add(voter); + } + + + public void RemoveKickVote(Client voter) + { + kickVoters.Remove(voter); + } + + public bool HasKickVoteFromID(int id) + { + return kickVoters.Any(k => k.ID == id); + } + + + public static void UpdateKickVotes(List connectedClients) + { + foreach (Client client in connectedClients) + { + client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter)); + } + } + + public void CancelTransfer() + { + if (FileStreamSender == null) return; + + FileStreamSender.CancelTransfer(); + FileStreamSender.Dispose(); + + FileStreamSender = null; + } + + + } +} diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 2d59d1f10..7c74fe45f 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -7,6 +7,7 @@ using FarseerPhysics; using System.IO; using System.Linq; using Barotrauma.Items.Components; +using System.ComponentModel; namespace Barotrauma.Networking { @@ -21,7 +22,10 @@ namespace Barotrauma.Networking private FileStreamReceiver fileStreamReceiver; private Queue> requestFileQueue; - private GUITickBox endRoundButton; + private GUIButton endRoundButton; + private GUITickBox endVoteTickBox; + + private ClientPermissions permissions; private bool connected; @@ -51,8 +55,22 @@ namespace Barotrauma.Networking public GameClient(string newName) { - endRoundButton = new GUITickBox(new Rectangle(GameMain.GraphicsWidth - 170, 20, 20, 20), "End round", Alignment.TopLeft, inGameHUD); - endRoundButton.OnSelected = ToggleEndRoundVote; + endVoteTickBox = new GUITickBox(new Rectangle(GameMain.GraphicsWidth - 170, 20, 20, 20), "End round", Alignment.TopLeft, inGameHUD); + endVoteTickBox.OnSelected = ToggleEndRoundVote; + endVoteTickBox.Visible = false; + + endRoundButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170, 20, 150, 20), "End round", Alignment.TopLeft, GUI.Style, inGameHUD); + endRoundButton.OnClicked = (btn, userdata) => + { + if (!permissions.HasFlag(ClientPermissions.EndRound)) return false; + + var msg = client.CreateMessage(); + + msg.Write((byte)PacketTypes.EndGame); + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + + return true; + }; endRoundButton.Visible = false; newName = newName.Replace(":", ""); @@ -227,9 +245,12 @@ namespace Barotrauma.Networking if (packetType == (byte)PacketTypes.LoggedIn) { myID = inc.ReadByte(); + permissions = (ClientPermissions)inc.ReadInt32(); gameStarted = inc.ReadBoolean(); bool hasCharacter = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean(); + + endRoundButton.Visible = permissions.HasFlag(ClientPermissions.EndRound); if (gameStarted && Screen.Selected != GameMain.GameScreen) { @@ -549,6 +570,46 @@ namespace Barotrauma.Networking AddChatMessage(otherClient.name + " has joined the server", ChatMessageType.Server); + break; + case (byte)PacketTypes.Permissions: + ClientPermissions newPermissions = (ClientPermissions)inc.ReadInt32(); + + if (newPermissions != permissions) + { + if (GUIMessageBox.MessageBoxes.Count > 0) + { + var existingMsgBox = GUIMessageBox.MessageBoxes.Peek(); + if (existingMsgBox.UserData as string == "permissions") + { + GUIMessageBox.MessageBoxes.Dequeue(); + } + } + + //new GUIMessageBox("The host has changed you permissions. New permissions") + string msg = ""; + if (newPermissions == ClientPermissions.None) + { + msg = "The host has removed all your special permissions."; + } + else + { + msg = "Your current permissions:\n"; + foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + { + if (!newPermissions.HasFlag(permissions) || permission == ClientPermissions.None) continue; + + System.Reflection.FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + + msg += " - " + attributes[0].Description+"\n"; + } + } + + new GUIMessageBox("Permissions changed", msg).UserData = "permissions"; + } + + endRoundButton.Visible = permissions.HasFlag(ClientPermissions.EndRound); + break; case (byte)PacketTypes.RequestFile: bool accepted = inc.ReadBoolean(); @@ -572,11 +633,9 @@ namespace Barotrauma.Networking //todo: unexpected file } else - { - + { fileStreamReceiver.ReadMessage(inc); } - break; case (byte)PacketTypes.PlayerLeft: @@ -653,7 +712,7 @@ namespace Barotrauma.Networking { if (Character != null) Character.Remove(); - endRoundButton.Selected = false; + endVoteTickBox.Selected = false; int seed = inc.ReadInt32(); string levelSeed = inc.ReadString(); @@ -746,7 +805,7 @@ namespace Barotrauma.Networking gameStarted = true; - endRoundButton.Visible = Voting.AllowEndVoting && myCharacter != null; + endVoteTickBox.Visible = Voting.AllowEndVoting && myCharacter != null; GameMain.GameScreen.Select(); @@ -804,6 +863,11 @@ namespace Barotrauma.Networking } + public bool HasPermission(ClientPermissions permission) + { + return permissions.HasFlag(permission); + } + public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { base.Draw(spriteBatch); @@ -839,6 +903,10 @@ namespace Barotrauma.Networking GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black * 0.7f, true); spriteBatch.DrawString(GUI.Font, "Network statistics:", new Vector2(x + 10, y + 10), Color.White); + spriteBatch.DrawString(GUI.Font, "Ping: " + (int)(client.ServerConnection.AverageRoundtripTime * 1000.0f) + " ms", new Vector2(x + 10, y + 25), Color.White); + + y += 15; + spriteBatch.DrawString(GUI.SmallFont, "Received bytes: " + client.Statistics.ReceivedBytes, new Vector2(x + 10, y + 45), Color.White); spriteBatch.DrawString(GUI.SmallFont, "Received packets: " + client.Statistics.ReceivedPackets, new Vector2(x + 10, y + 60), Color.White); @@ -866,21 +934,35 @@ namespace Barotrauma.Networking Character character = obj as Character; if (character == null) return false; - if (character != myCharacter && Voting.AllowVoteKick) + if (character != myCharacter) { var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == character); - if (client != null) + if (client == null) return false; + + if (HasPermission(ClientPermissions.Ban)) { - var kickButton = new GUIButton(new Rectangle(0, 0, 120, 20), "Vote to Kick", Alignment.BottomLeft, GUI.Style, characterFrame); + var banButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Ban", Alignment.BottomRight, GUI.Style, characterFrame); + banButton.UserData = character.Name; + banButton.OnClicked += GameMain.NetLobbyScreen.BanPlayer; + } + if (HasPermission(ClientPermissions.Kick)) + { + var kickButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Kick", Alignment.BottomLeft, GUI.Style, characterFrame); + kickButton.UserData = character.Name; + kickButton.OnClicked += GameMain.NetLobbyScreen.KickPlayer; + } + else if (Voting.AllowVoteKick) + { + var kickVoteButton = new GUIButton(new Rectangle(0, 0, 120, 20), "Vote to Kick", Alignment.BottomLeft, GUI.Style, characterFrame); if (GameMain.NetworkMember.ConnectedClients != null) { - kickButton.Enabled = !client.HasKickVoteFromID(myID); + kickVoteButton.Enabled = !client.HasKickVoteFromID(myID); } - kickButton.UserData = character; - kickButton.OnClicked += VoteForKick; - } + kickVoteButton.UserData = character; + kickVoteButton.OnClicked += VoteForKick; + } } return true; @@ -981,6 +1063,19 @@ namespace Barotrauma.Networking client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } + public override void KickPlayer(string kickedName, bool ban) + { + if (!permissions.HasFlag(ClientPermissions.Kick) && !ban) return; + if (!permissions.HasFlag(ClientPermissions.Ban) && ban) return; + + NetOutgoingMessage msg = client.CreateMessage(); + msg.Write((byte)PacketTypes.KickPlayer); + msg.Write(ban); + msg.Write(kickedName); + + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); + } + public bool VoteForKick(GUIButton button, object userdata) { var votedClient = otherClients.Find(c => c.Character == userdata); @@ -1105,7 +1200,7 @@ namespace Barotrauma.Networking Character.Controlled = character; GameMain.LightManager.LosEnabled = true; - if (endRoundButton != null) endRoundButton.Visible = Voting.AllowEndVoting; + if (endVoteTickBox != null) endVoteTickBox.Visible = Voting.AllowEndVoting; } return character; diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index 3db49de8c..56f0ebe79 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -87,7 +87,7 @@ namespace Barotrauma.Networking //---------------------------------------- var endRoundButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170, 20, 150, 20), "End round", Alignment.TopLeft, GUI.Style, inGameHUD); - endRoundButton.OnClicked = EndButtonHit; + endRoundButton.OnClicked = (btn, userdata) => { EndGame(); return true; }; log = new ServerLog(name); showLogButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 170 - 170, 20, 150, 20), "Server Log", Alignment.TopLeft, GUI.Style, inGameHUD); @@ -108,9 +108,9 @@ namespace Barotrauma.Networking banList = new BanList(); LoadSettings(); + LoadClientPermissions(); //---------------------------------------- - CoroutineManager.StartCoroutine(StartServer(isPublic)); } @@ -312,8 +312,8 @@ namespace Barotrauma.Networking { Log("Ending round (submarine reached the end of the level)", Color.Cyan); } - - EndButtonHit(null, null); + + EndGame(); UpdateNetLobby(null,null); return; } @@ -496,11 +496,7 @@ namespace Barotrauma.Networking break; case (byte)PacketTypes.Chatmessage: - //SendChatMessage(ChatMessage.ReadNetworkMessage(inc)); - //!!!!!!!!!!! - ReadChatMessage(inc); - break; case (byte)PacketTypes.PlayerLeft: DisconnectClient(inc.SenderConnection); @@ -508,6 +504,38 @@ namespace Barotrauma.Networking case (byte)PacketTypes.StartGame: sender.ReadyToStart = true; break; + case (byte)PacketTypes.EndGame: + if (!sender.HasPermission(ClientPermissions.EndRound)) + { + Log(sender.name+" attempted to end the round (insufficient permissions)", Color.Red); + } + else + { + Log("Round ended by " + sender.name, Color.Red); + EndGame(); + } + break; + case (byte)PacketTypes.KickPlayer: + bool ban = inc.ReadBoolean(); + string kickedName = inc.ReadString(); + + var kickedClient = connectedClients.Find(c => c.name.ToLowerInvariant() == kickedName.ToLowerInvariant()); + if (kickedClient == null || kickedClient == sender) return; + + if (ban && !sender.HasPermission(ClientPermissions.Ban)) + { + Log(sender.name + " attempted to ban " + kickedClient.name + " (insufficient permissions)", Color.Red); + + } + else if (!sender.HasPermission(ClientPermissions.Kick)) + { + Log(sender.name + " attempted to kick " + kickedClient.name + " (insufficient permissions)", Color.Red); + } + else + { + KickClient(kickedClient, ban); + } + break; case (byte)PacketTypes.CharacterInfo: ReadCharacterData(inc); break; @@ -565,7 +593,7 @@ namespace Barotrauma.Networking ((float)EndVoteCount / (float)EndVoteMax) >= EndVoteRequiredRatio) { Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", Color.Cyan); - EndButtonHit(null, null); + EndGame(); } break; case (byte)PacketTypes.RequestNetLobbyUpdate: @@ -578,7 +606,6 @@ namespace Barotrauma.Networking var startMessage = CreateStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset); server.SendMessage(startMessage, inc.SenderConnection, NetDeliveryMethod.ReliableOrdered); - sender.Spectating = true; CoroutineManager.StartCoroutine(SyncSpectator(sender)); } break; @@ -619,7 +646,7 @@ namespace Barotrauma.Networking if (recipients == null) { - recipients = connectedClients.FindAll(c => c.Character != null || c.Spectating); + recipients = connectedClients.FindAll(c => c.inGame); } if (recipients.Count == 0) return; @@ -1002,9 +1029,9 @@ namespace Barotrauma.Networking return msg; } - private bool EndButtonHit(GUIButton button, object obj) + public void EndGame() { - if (!gameStarted) return false; + if (!gameStarted) return; string endMessage = "The round has ended." + '\n'; @@ -1018,12 +1045,7 @@ namespace Barotrauma.Networking if (autoRestart) AutoRestartTimer = AutoRestartInterval; if (SaveServerLogs) log.Save(); - - return true; - } - - public IEnumerable EndGame(string endMessage) - { + Character.Controlled = null; myCharacter = null; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; @@ -1037,7 +1059,6 @@ namespace Barotrauma.Networking #endif respawnManager = null; - gameStarted = false; if (connectedClients.Count > 0) @@ -1053,12 +1074,16 @@ namespace Barotrauma.Networking foreach (Client client in connectedClients) { - client.Spectating = false; client.Character = null; client.inGame = false; } } + CoroutineManager.StartCoroutine(EndCinematic()); + } + + public IEnumerable EndCinematic() + { float endPreviewLength = 10.0f; var cinematic = new TransitionCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); @@ -1077,7 +1102,6 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.Select(); yield return CoroutineStatus.Success; - } public void SendRespawnManagerMsg(List spawnedCharacters = null, List spawnedItems = null, List recipients = null) @@ -1163,7 +1187,7 @@ namespace Barotrauma.Networking //if (GameMain.GameSession!=null) GameMain.GameSession.CrewManager.CreateCrewFrame(crew); } - public void KickPlayer(string playerName, bool ban = false) + public override void KickPlayer(string playerName, bool ban) { playerName = playerName.ToLowerInvariant(); @@ -1386,6 +1410,27 @@ namespace Barotrauma.Networking return true; } + public void UpdateClientPermissions(Client client) + { + var msg = server.CreateMessage(); + msg.Write((byte)PacketTypes.Permissions); + msg.Write((int)client.Permissions); + + server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered); + + clientPermissions.RemoveAll(cp => cp.IP == client.Connection.RemoteEndPoint.Address.ToString()); + + if (client.Permissions != ClientPermissions.None) + { + clientPermissions.Add(new SavedClientPermission( + client.name, + client.Connection.RemoteEndPoint.Address.ToString(), + client.Permissions)); + } + + SaveClientPermissions(); + } + public override bool SelectCrewCharacter(GUIComponent component, object obj) { base.SelectCrewCharacter(component, obj); @@ -1794,131 +1839,4 @@ namespace Barotrauma.Networking server.Shutdown("The server has been shut down"); } } - - class Client - { - public string name; - public byte ID; - - public Character Character; - public CharacterInfo characterInfo; - public NetConnection Connection { get; set; } - public string version; - public bool inGame; - - private List kickVoters; - - public bool ReadyToStart; - - private object[] votes; - - public List jobPreferences; - public JobPrefab assignedJob; - - public FileStreamSender FileStreamSender; - - public bool Spectating; - - public ReliableChannel ReliableChannel; - - public float deleteDisconnectedTimer; - - public int KickVoteCount - { - get { return kickVoters.Count; } - } - - - public Client(NetPeer server, string name, byte ID) - : this(name, ID) - { - ReliableChannel = new ReliableChannel(server); - } - - public Client(string name, byte ID) - { - this.name = name; - this.ID = ID; - - kickVoters = new List(); - - votes = new object[Enum.GetNames(typeof(VoteType)).Length]; - - jobPreferences = new List(JobPrefab.List.GetRange(0,3)); - } - - public static bool IsValidName(string name) - { - if (name.Contains("\n") || name.Contains("\r\n")) return false; - - return (name.All(c => - c != ';' && - c != ',' && - c != '<' && - c != '/')); - } - - public static string SanitizeName(string name) - { - if (name.Length > 20) - { - name = name.Substring(0, 20); - } - - return name; - } - - public T GetVote(VoteType voteType) - { - return (votes[(int)voteType] is T) ? (T)votes[(int)voteType] : default(T); - } - - public void SetVote(VoteType voteType, object value) - { - votes[(int)voteType] = value; - } - - public void ResetVotes() - { - for (int i = 0; i k.ID == id); - } - - - public static void UpdateKickVotes(List connectedClients) - { - foreach (Client client in connectedClients) - { - client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter)); - } - } - - public void CancelTransfer() - { - if (FileStreamSender == null) return; - - FileStreamSender.CancelTransfer(); - FileStreamSender.Dispose(); - - FileStreamSender = null; - } - } } diff --git a/Subsurface/Source/Networking/GameServerLogin.cs b/Subsurface/Source/Networking/GameServerLogin.cs index 45b20e0f9..5b0e54df3 100644 --- a/Subsurface/Source/Networking/GameServerLogin.cs +++ b/Subsurface/Source/Networking/GameServerLogin.cs @@ -177,6 +177,12 @@ namespace Barotrauma.Networking newClient.Connection = inc.SenderConnection; newClient.version = version; + var savedPermissions = clientPermissions.Find(cp => cp.IP == newClient.Connection.RemoteEndPoint.Address.ToString()); + if (savedPermissions != null) + { + newClient.SetPermissions(savedPermissions.Permissions); + } + connectedClients.Add(newClient); UpdateCrewFrame(); @@ -214,6 +220,7 @@ namespace Barotrauma.Networking outmsg.Write((byte)PacketTypes.LoggedIn); outmsg.Write(sender.ID); + outmsg.Write((int)sender.Permissions); outmsg.Write(gameStarted); outmsg.Write(gameStarted && sender.Character != null && !sender.Character.IsDead); outmsg.Write(AllowSpectating); diff --git a/Subsurface/Source/Networking/GameServerSettings.cs b/Subsurface/Source/Networking/GameServerSettings.cs index 2a3bbc0d9..ad6ce7df7 100644 --- a/Subsurface/Source/Networking/GameServerSettings.cs +++ b/Subsurface/Source/Networking/GameServerSettings.cs @@ -21,7 +21,24 @@ namespace Barotrauma.Networking partial class GameServer : NetworkMember, IPropertyObject { + private class SavedClientPermission + { + public readonly string IP; + public readonly string Name; + + public ClientPermissions Permissions; + + public SavedClientPermission(string name, string ip, ClientPermissions permissions) + { + this.Name = name; + this.IP = ip; + + this.Permissions = permissions; + } + } + public const string SettingsFile = "serversettings.xml"; + public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.txt"; public Dictionary ObjectProperties { @@ -50,6 +67,8 @@ namespace Barotrauma.Networking private bool autoRestart; + private List clientPermissions = new List(); + [HasDefaultValue(true, true)] public bool RandomizeSeed { @@ -504,6 +523,60 @@ namespace Barotrauma.Networking banList.CreateBanFrame(settingsTabs[2]); + + } + + public void LoadClientPermissions() + { + if (!File.Exists(ClientPermissionsFile)) return; + + string[] lines; + try + { + lines = File.ReadAllLines(ClientPermissionsFile); + } + catch (Exception e) + { + DebugConsole.ThrowError("Failed to open client permission file " + ClientPermissionsFile, e); + return; + } + + clientPermissions.Clear(); + foreach (string line in lines) + { + string[] separatedLine = line.Split('|'); + if (separatedLine.Length < 3) continue; + + string name = String.Join("|", separatedLine.Take(separatedLine.Length - 2)); + string ip = separatedLine[separatedLine.Length - 2]; + + ClientPermissions permissions = ClientPermissions.None; + if (Enum.TryParse(separatedLine.Last(), out permissions)) + { + clientPermissions.Add(new SavedClientPermission(name, ip, permissions)); + } + } + } + + public void SaveClientPermissions() + { + GameServer.Log("Saving client permissions", null); + + List lines = new List(); + + foreach (SavedClientPermission clientPermission in clientPermissions) + { + lines.Add(clientPermission.Name + "|" + clientPermission.IP+"|"+clientPermission.Permissions.ToString()); + } + + try + { + File.WriteAllLines(ClientPermissionsFile, lines); + } + catch (Exception e) + { + DebugConsole.ThrowError("Saving client permissions to " + ClientPermissionsFile + " failed", e); + } } private bool SwitchSubSelection(GUITickBox tickBox) diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 19388ae4b..9b6ac1dad 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -17,6 +17,10 @@ namespace Barotrauma.Networking PlayerJoined, PlayerLeft, + KickPlayer, + + Permissions, + RequestNetLobbyUpdate, StartGame, EndGame, CanStartGame, @@ -330,6 +334,8 @@ namespace Barotrauma.Networking public virtual void SendChatMessage(string message, ChatMessageType? type = null) { } + public virtual void KickPlayer(string kickedName, bool ban) { } + public virtual void Update(float deltaTime) { if (gameStarted && Screen.Selected == GameMain.GameScreen) diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index b65822043..aa205bf1c 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -9,6 +9,8 @@ using FarseerPhysics.Dynamics; using System.IO; using System.Linq; using System.Collections.Generic; +using System.Reflection; +using System.ComponentModel; namespace Barotrauma { @@ -346,7 +348,7 @@ namespace Barotrauma subList.Enabled = GameMain.Server != null || GameMain.NetworkMember.Voting.AllowSubVoting; shuttleList.Enabled = subList.Enabled; - playerList.Enabled = GameMain.Server != null; + //playerList.Enabled = GameMain.Server != null; modeList.Enabled = GameMain.Server != null || GameMain.NetworkMember.Voting.AllowModeVoting; seedBox.Enabled = GameMain.Server != null; serverMessage.Enabled = GameMain.Server != null; @@ -758,24 +760,91 @@ namespace Barotrauma private bool SelectPlayer(GUIComponent component, object obj) { + if (GameMain.Client != null) + { + if (!GameMain.Client.HasPermission(ClientPermissions.Ban) && + !GameMain.Client.HasPermission(ClientPermissions.Kick)) + { + return false; + } + } + playerFrame = new GUIFrame(new Rectangle(0, 0, 0, 0), Color.Black * 0.3f); - var playerFrameInner = new GUIFrame(new Rectangle(0,0,300,150), null, Alignment.Center, GUI.Style, playerFrame); + var playerFrameInner = new GUIFrame(new Rectangle(0, 0, 300, 150), null, Alignment.Center, GUI.Style, playerFrame); playerFrameInner.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f); new GUITextBlock(new Rectangle(0,0,200,20), component.UserData.ToString(), GUI.Style, Alignment.TopLeft, Alignment.TopLeft, playerFrameInner, false, GUI.LargeFont); - var kickButton = new GUIButton(new Rectangle(0, -30, 100, 20), "Kick", Alignment.BottomLeft, GUI.Style, playerFrameInner); - kickButton.UserData = obj; - kickButton.OnClicked += KickPlayer; - kickButton.OnClicked += ClosePlayerFrame; + if (GameMain.Server != null) + { + var selectedClient = GameMain.Server.ConnectedClients.Find(c => c.name == component.UserData.ToString()); - var banButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Ban", Alignment.BottomLeft, GUI.Style, playerFrameInner); - banButton.UserData = obj; - banButton.OnClicked += BanPlayer; - banButton.OnClicked += ClosePlayerFrame; + new GUITextBlock(new Rectangle(0, 25, 150, 15), selectedClient.Connection.RemoteEndPoint.Address.ToString(), GUI.Style, playerFrameInner); + + //var permissionsBox = new GUIFrame(new Rectangle(0, 60, 0, 85), GUI.Style, playerFrameInner); + //permissionsBox.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f); + //permissionsBox.UserData = selectedClient; + + //new GUITextBlock(new Rectangle(0, 0, 0, 15), "Permissions:", GUI.Style, permissionsBox); + //int x = 0, y = 0; + //foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions))) + //{ + // if (permission == ClientPermissions.None) continue; + + // FieldInfo fi = typeof(ClientPermissions).GetField(permission.ToString()); + // DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + + // string permissionStr = attributes.Length > 0 ? attributes[0].Description : permission.ToString(); + + // var permissionTick = new GUITickBox(new Rectangle(x,y+20,15,15), permissionStr, Alignment.TopLeft, GUI.SmallFont, permissionsBox); + // permissionTick.UserData = permission; + // permissionTick.Selected = selectedClient.HasPermission(permission); + + // permissionTick.OnSelected = (tickBox) => + // { + // var client = tickBox.Parent.UserData as Client; + // if (client == null) return false; + + // var thisPermission = (ClientPermissions)tickBox.UserData; + + // if (tickBox.Selected) + // client.GivePermission(thisPermission); + // else + // client.RemovePermission(thisPermission); + + // GameMain.Server.UpdateClientPermissions(client); + + // return true; + // }; + + + // y += 20; + // if (y >= permissionsBox.Rect.Height -20) + // { + // y = 0; + // x += 100; + // } + //} + } + + if (GameMain.Server != null || GameMain.Client.HasPermission(ClientPermissions.Kick)) + { + var kickButton = new GUIButton(new Rectangle(0, -30, 100, 20), "Kick", Alignment.BottomLeft, GUI.Style, playerFrameInner); + kickButton.UserData = obj; + kickButton.OnClicked += KickPlayer; + kickButton.OnClicked += ClosePlayerFrame; + } + + if (GameMain.Server != null || GameMain.Client.HasPermission(ClientPermissions.Ban)) + { + var banButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Ban", Alignment.BottomLeft, GUI.Style, playerFrameInner); + banButton.UserData = obj; + banButton.OnClicked += BanPlayer; + banButton.OnClicked += ClosePlayerFrame; + } var closeButton = new GUIButton(new Rectangle(0, 0, 100, 20), "Close", Alignment.BottomRight, GUI.Style, playerFrameInner); closeButton.OnClicked = ClosePlayerFrame; @@ -792,18 +861,32 @@ namespace Barotrauma public bool KickPlayer(GUIButton button, object userData) { - if (GameMain.Server == null || userData == null) return false; + if (userData == null) return false; - GameMain.Server.KickPlayer(userData.ToString()); + if (GameMain.Server!=null) + { + GameMain.Server.KickPlayer(userData.ToString(), false); + } + else if (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.Kick)) + { + GameMain.Client.KickPlayer(userData.ToString(), false); + } return false; } public bool BanPlayer(GUIButton button, object userData) { - if (GameMain.Server == null || userData == null) return false; + if (userData == null) return false; - GameMain.Server.KickPlayer(userData.ToString(), true); + if (GameMain.Server != null) + { + GameMain.Server.KickPlayer(userData.ToString(), true); + } + else if (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.Ban)) + { + GameMain.Client.KickPlayer(userData.ToString(), true); + } return false; } @@ -913,15 +996,6 @@ namespace Barotrauma if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f; } - - //public bool EnterChatMessage(GUITextBox textBox, string message) - //{ - // if (String.IsNullOrEmpty(message)) return false; - - // GameMain.NetworkMember.SendChatMessage(ChatMessage.Create(message, ChatMessageType.Default, null)); - - // return true; - //} private void UpdatePreviewPlayer(CharacterInfo characterInfo) {