Merge branch 'dedicated-server' (TODO: make sure I didn't break anything)
Conflicts: Barotrauma/Barotrauma.csproj Barotrauma/BarotraumaShared/Source/Characters/AI/AIController.cs Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs Barotrauma/BarotraumaShared/Source/Characters/Attack.cs Barotrauma/BarotraumaShared/Source/Characters/Character.cs Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs Barotrauma/BarotraumaShared/Source/Characters/Limb.cs Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs Barotrauma/BarotraumaShared/Source/Map/Explosion.cs
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class BannedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
|
||||
public bool CompareTo(string ipCompare)
|
||||
{
|
||||
int rangeBanIndex = IP.IndexOf(".x");
|
||||
if (rangeBanIndex<=-1)
|
||||
{
|
||||
return ipCompare == IP;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ipCompare.Length < rangeBanIndex) return false;
|
||||
return ipCompare.Substring(0, rangeBanIndex) == IP.Substring(0, rangeBanIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public BannedPlayer(string name, string ip)
|
||||
{
|
||||
this.Name = name;
|
||||
this.IP = ip;
|
||||
}
|
||||
}
|
||||
|
||||
partial class BanList
|
||||
{
|
||||
const string SavePath = "Data/bannedplayers.txt";
|
||||
|
||||
private List<BannedPlayer> bannedPlayers;
|
||||
|
||||
public BanList()
|
||||
{
|
||||
bannedPlayers = new List<BannedPlayer>();
|
||||
|
||||
if (File.Exists(SavePath))
|
||||
{
|
||||
string[] lines;
|
||||
try
|
||||
{
|
||||
lines = File.ReadAllLines(SavePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to open the list of banned players in " + SavePath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string[] separatedLine = line.Split(',');
|
||||
if (separatedLine.Length < 2) continue;
|
||||
|
||||
string name = String.Join(",", separatedLine.Take(separatedLine.Length - 1));
|
||||
string ip = separatedLine.Last();
|
||||
|
||||
bannedPlayers.Add(new BannedPlayer(name, ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void BanPlayer(string name, string ip)
|
||||
{
|
||||
if (bannedPlayers.Any(bp => bp.IP == ip)) return;
|
||||
|
||||
DebugConsole.Log("Banned " + name);
|
||||
|
||||
bannedPlayers.Add(new BannedPlayer(name, ip));
|
||||
Save();
|
||||
}
|
||||
|
||||
public bool IsBanned(string IP)
|
||||
{
|
||||
return bannedPlayers.Any(bp => bp.CompareTo(IP));
|
||||
}
|
||||
|
||||
private void RemoveBan(BannedPlayer banned)
|
||||
{
|
||||
DebugConsole.Log("Removing ban from " + banned.Name);
|
||||
GameServer.Log("Removing ban from " + banned.Name, ServerLog.MessageType.ServerMessage);
|
||||
|
||||
bannedPlayers.Remove(banned);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public string ToRange(string ip)
|
||||
{
|
||||
for (int i = ip.Length - 1; i > 0; i--)
|
||||
{
|
||||
if (ip[i] == '.')
|
||||
{
|
||||
ip = ip.Substring(0, i) + ".x";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
private void RangeBan(BannedPlayer banned)
|
||||
{
|
||||
banned.IP = ToRange(banned.IP);
|
||||
|
||||
BannedPlayer bp;
|
||||
while ((bp = bannedPlayers.Find(x => banned.CompareTo(x.IP))) != null)
|
||||
{
|
||||
//remove all specific bans that are now covered by the rangeban
|
||||
bannedPlayers.Remove(bp);
|
||||
}
|
||||
|
||||
bannedPlayers.Add(banned);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
GameServer.Log("Saving banlist", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
|
||||
foreach (BannedPlayer banned in bannedPlayers)
|
||||
{
|
||||
lines.Add(banned.Name + "," + banned.IP);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(SavePath, lines);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving the list of banned players to " + SavePath + " failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum ChatMessageType
|
||||
{
|
||||
Default, Error, Dead, Server, Radio, Private
|
||||
}
|
||||
|
||||
class ChatMessage
|
||||
{
|
||||
public const int MaxLength = 150;
|
||||
|
||||
public const int MaxMessagesPerPacket = 10;
|
||||
|
||||
public const float SpeakRange = 2000.0f;
|
||||
|
||||
public static Color[] MessageColor =
|
||||
{
|
||||
new Color(125, 140, 153), //default
|
||||
new Color(204, 74, 78), //error
|
||||
new Color(63, 72, 204), //dead
|
||||
new Color(157, 225, 160), //server
|
||||
new Color(238, 208, 0), //radio
|
||||
new Color(228, 199, 27) //private
|
||||
};
|
||||
|
||||
public readonly string Text;
|
||||
|
||||
public ChatMessageType Type;
|
||||
|
||||
public readonly Character Sender;
|
||||
|
||||
public readonly string SenderName;
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get { return MessageColor[(int)Type]; }
|
||||
}
|
||||
|
||||
public string TextWithSender
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static UInt16 LastID = 0;
|
||||
|
||||
public UInt16 NetStateID
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private ChatMessage(string senderName, string text, ChatMessageType type, Character sender)
|
||||
{
|
||||
Text = text;
|
||||
Type = type;
|
||||
|
||||
Sender = sender;
|
||||
|
||||
SenderName = senderName;
|
||||
|
||||
TextWithSender = string.IsNullOrWhiteSpace(senderName) ? text : senderName + ": " + text;
|
||||
}
|
||||
|
||||
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender)
|
||||
{
|
||||
return new ChatMessage(senderName, text, type, sender);
|
||||
}
|
||||
|
||||
public static string GetChatMessageCommand(string message, out string messageWithoutCommand)
|
||||
{
|
||||
messageWithoutCommand = message;
|
||||
|
||||
int separatorIndex = message.IndexOf(";");
|
||||
if (separatorIndex == -1) return "";
|
||||
|
||||
//int colonIndex = message.IndexOf(":");
|
||||
|
||||
string command = "";
|
||||
try
|
||||
{
|
||||
command = message.Substring(0, separatorIndex);
|
||||
command = command.Trim();
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
messageWithoutCommand = message.Substring(separatorIndex + 1, message.Length - separatorIndex - 1).TrimStart();
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
public string ApplyDistanceEffect(Character listener)
|
||||
{
|
||||
if (Sender == null) return Text;
|
||||
|
||||
return ApplyDistanceEffect(listener, Sender, Text, SpeakRange);
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(Entity listener, Entity Sender, string text, float range)
|
||||
{
|
||||
if (listener.WorldPosition == Sender.WorldPosition) return text;
|
||||
|
||||
float dist = Vector2.Distance(listener.WorldPosition, Sender.WorldPosition);
|
||||
if (dist > range) return "";
|
||||
|
||||
if (Submarine.CheckVisibility(listener.SimPosition, Sender.SimPosition) != null) dist *= 2.0f;
|
||||
if (dist > range) return "";
|
||||
|
||||
float garbleAmount = dist / range;
|
||||
if (garbleAmount < 0.5f) return text;
|
||||
|
||||
int startIndex = Math.Max(text.IndexOf(':') + 1, 1);
|
||||
|
||||
StringBuilder sb = new StringBuilder(text.Length);
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
sb.Append((i>startIndex && Rand.Range(0.0f, 1.0f) < garbleAmount) ? '-' : text[i]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void ClientWrite(NetOutgoingMessage msg)
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.Write(Text);
|
||||
}
|
||||
|
||||
public static void ServerRead(NetIncomingMessage msg, Client c)
|
||||
{
|
||||
UInt16 ID = msg.ReadUInt16();
|
||||
string txt = msg.ReadString();
|
||||
if (txt == null) txt = "";
|
||||
|
||||
if (!NetIdUtils.IdMoreRecent(ID, c.lastSentChatMsgID)) return;
|
||||
|
||||
c.lastSentChatMsgID = ID;
|
||||
|
||||
if (txt.Length > MaxLength)
|
||||
{
|
||||
txt = txt.Substring(0, MaxLength);
|
||||
}
|
||||
|
||||
c.lastSentChatMessages.Add(txt);
|
||||
if (c.lastSentChatMessages.Count > 10)
|
||||
{
|
||||
c.lastSentChatMessages.RemoveRange(0, c.lastSentChatMessages.Count-10);
|
||||
}
|
||||
|
||||
float similarity = 0.0f;
|
||||
for (int i = 0; i < c.lastSentChatMessages.Count; i++)
|
||||
{
|
||||
float closeFactor = 1.0f / (c.lastSentChatMessages.Count - i);
|
||||
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.lastSentChatMessages[i]);
|
||||
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
|
||||
}
|
||||
|
||||
if (similarity + c.ChatSpamSpeed > 5.0f)
|
||||
{
|
||||
c.ChatSpamCount++;
|
||||
|
||||
if (c.ChatSpamCount > 3)
|
||||
{
|
||||
//kick for spamming too much
|
||||
GameMain.Server.KickClient(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChatMessage denyMsg = ChatMessage.Create("", "You have been blocked by the spam filter. Try again after 10 seconds.", ChatMessageType.Server, null);
|
||||
c.ChatSpamTimer = 10.0f;
|
||||
GameMain.Server.SendChatMessage(denyMsg, c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
c.ChatSpamSpeed += similarity + 0.5f;
|
||||
|
||||
if (c.ChatSpamTimer > 0.0f)
|
||||
{
|
||||
ChatMessage denyMsg = ChatMessage.Create("", "You have been blocked by the spam filter. Try again after 10 seconds.", ChatMessageType.Server, null);
|
||||
c.ChatSpamTimer = 10.0f;
|
||||
GameMain.Server.SendChatMessage(denyMsg, c);
|
||||
return;
|
||||
}
|
||||
|
||||
GameMain.Server.SendChatMessage(txt, null, c);
|
||||
}
|
||||
|
||||
public int EstimateLengthBytesClient()
|
||||
{
|
||||
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
|
||||
2 + //(UInt16)NetStateID
|
||||
Encoding.UTF8.GetBytes(Text).Length + 2;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public int EstimateLengthBytesServer(Client c)
|
||||
{
|
||||
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
|
||||
2 + //(UInt16)NetStateID
|
||||
1 + //(byte)Type
|
||||
Encoding.UTF8.GetBytes(Text).Length + 2;
|
||||
|
||||
if (Sender != null && c.inGame)
|
||||
{
|
||||
length += 2; //sender ID (UInt16)
|
||||
}
|
||||
else if (SenderName != null)
|
||||
{
|
||||
length += Encoding.UTF8.GetBytes(SenderName).Length + 2;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetOutgoingMessage msg, Client c)
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.Write((byte)Type);
|
||||
msg.Write(Text);
|
||||
|
||||
msg.Write(Sender != null && c.inGame);
|
||||
if (Sender != null && c.inGame)
|
||||
{
|
||||
msg.Write(Sender.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(SenderName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClientRead(NetIncomingMessage msg)
|
||||
{
|
||||
UInt16 ID = msg.ReadUInt16();
|
||||
ChatMessageType type = (ChatMessageType)msg.ReadByte();
|
||||
string txt = msg.ReadString();
|
||||
|
||||
string senderName = "";
|
||||
Character senderCharacter = null;
|
||||
bool hasSenderCharacter = msg.ReadBoolean();
|
||||
if (hasSenderCharacter)
|
||||
{
|
||||
senderCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
|
||||
if (senderCharacter != null)
|
||||
{
|
||||
senderName = senderCharacter.Name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
senderName = msg.ReadString();
|
||||
}
|
||||
|
||||
if (NetIdUtils.IdMoreRecent(ID, LastID))
|
||||
{
|
||||
GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter);
|
||||
LastID = ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
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 byte TeamID = 0;
|
||||
|
||||
public Character Character;
|
||||
public CharacterInfo characterInfo;
|
||||
public NetConnection Connection { get; set; }
|
||||
public bool inGame;
|
||||
public UInt16 lastRecvGeneralUpdate = 0;
|
||||
|
||||
public UInt16 lastSentChatMsgID = 0; //last msg this client said
|
||||
public UInt16 lastRecvChatMsgID = 0; //last msg this client knows about
|
||||
|
||||
public UInt16 lastSentEntityEventID = 0;
|
||||
public UInt16 lastRecvEntityEventID = 0;
|
||||
|
||||
public List<ChatMessage> chatMsgQueue = new List<ChatMessage>();
|
||||
public UInt16 lastChatMsgQueueID;
|
||||
|
||||
|
||||
//latest chat messages sent by this client
|
||||
public List<string> lastSentChatMessages = new List<string>();
|
||||
public float ChatSpamSpeed;
|
||||
public float ChatSpamTimer;
|
||||
public int ChatSpamCount;
|
||||
|
||||
public double MidRoundSyncTimeOut;
|
||||
|
||||
public bool NeedsMidRoundSync;
|
||||
//how many unique events the client missed before joining the server
|
||||
public UInt16 UnreceivedEntityEventCount;
|
||||
public UInt16 FirstNewEventID;
|
||||
|
||||
private List<Client> kickVoters;
|
||||
|
||||
//when was a specific entity event last sent to the client
|
||||
// key = event id, value = NetTime.Now when sending
|
||||
public Dictionary<UInt16, float> entityEventLastSent;
|
||||
|
||||
private Queue<Entity> pendingPositionUpdates = new Queue<Entity>();
|
||||
|
||||
public bool ReadyToStart;
|
||||
|
||||
private object[] votes;
|
||||
|
||||
public List<JobPrefab> jobPreferences;
|
||||
public JobPrefab assignedJob;
|
||||
|
||||
public float deleteDisconnectedTimer;
|
||||
|
||||
public ClientPermissions Permissions = ClientPermissions.None;
|
||||
|
||||
public Queue<Entity> PendingPositionUpdates
|
||||
{
|
||||
get { return pendingPositionUpdates; }
|
||||
}
|
||||
|
||||
public void InitClientSync()
|
||||
{
|
||||
lastSentChatMsgID = 0;
|
||||
lastRecvChatMsgID = ChatMessage.LastID;
|
||||
|
||||
lastRecvGeneralUpdate = 0;
|
||||
|
||||
lastRecvEntityEventID = 0;
|
||||
|
||||
UnreceivedEntityEventCount = 0;
|
||||
NeedsMidRoundSync = false;
|
||||
}
|
||||
|
||||
public int KickVoteCount
|
||||
{
|
||||
get { return kickVoters.Count; }
|
||||
}
|
||||
|
||||
public Client(NetPeer server, string name, byte ID)
|
||||
: this(name, ID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Client(string name, byte ID)
|
||||
{
|
||||
this.name = name;
|
||||
this.ID = ID;
|
||||
|
||||
kickVoters = new List<Client>();
|
||||
|
||||
votes = new object[Enum.GetNames(typeof(VoteType)).Length];
|
||||
|
||||
jobPreferences = new List<JobPrefab>(JobPrefab.List.GetRange(0, 3));
|
||||
|
||||
entityEventLastSent = new Dictionary<UInt16, float>();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
name = name.Trim();
|
||||
if (name.Length > 20)
|
||||
{
|
||||
name = name.Substring(0, 20);
|
||||
}
|
||||
string rName = "";
|
||||
for (int i=0;i<name.Length;i++)
|
||||
{
|
||||
if (name[i] < 32)
|
||||
{
|
||||
rName += '?';
|
||||
}
|
||||
else
|
||||
{
|
||||
rName += name[i];
|
||||
}
|
||||
}
|
||||
|
||||
return rName;
|
||||
}
|
||||
|
||||
public void SetPermissions(ClientPermissions permissions)
|
||||
{
|
||||
this.Permissions = permissions;
|
||||
}
|
||||
|
||||
public void GivePermission(ClientPermissions permission)
|
||||
{
|
||||
if (!this.Permissions.HasFlag(permission)) this.Permissions |= permission;
|
||||
}
|
||||
|
||||
public void RemovePermission(ClientPermissions permission)
|
||||
{
|
||||
if (this.Permissions.HasFlag(permission)) this.Permissions &= ~permission;
|
||||
}
|
||||
|
||||
public bool HasPermission(ClientPermissions permission)
|
||||
{
|
||||
return this.Permissions.HasFlag(permission);
|
||||
}
|
||||
|
||||
public T GetVote<T>(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<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EntitySpawner : Entity, IServerSerializable
|
||||
{
|
||||
const int MaxEntitiesPerWrite = 10;
|
||||
|
||||
private enum SpawnableType { Item, Character };
|
||||
|
||||
interface IEntitySpawnInfo
|
||||
{
|
||||
Entity Spawn();
|
||||
}
|
||||
|
||||
class ItemSpawnInfo : IEntitySpawnInfo
|
||||
{
|
||||
public readonly ItemPrefab Prefab;
|
||||
|
||||
public readonly Vector2 Position;
|
||||
public readonly Inventory Inventory;
|
||||
public readonly Submarine Submarine;
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Position = worldPosition;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Position = position;
|
||||
Submarine = sub;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Inventory = inventory;
|
||||
}
|
||||
|
||||
public Entity Spawn()
|
||||
{
|
||||
Item spawnedItem = null;
|
||||
|
||||
if (Inventory != null)
|
||||
{
|
||||
spawnedItem = new Item(Prefab, Vector2.Zero, null);
|
||||
Inventory.TryPutItem(spawnedItem, spawnedItem.AllowedSlots);
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnedItem = new Item(Prefab, Position, Submarine);
|
||||
}
|
||||
|
||||
return spawnedItem;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Queue<IEntitySpawnInfo> spawnQueue;
|
||||
private readonly Queue<Entity> removeQueue;
|
||||
|
||||
class SpawnOrRemove
|
||||
{
|
||||
public readonly Entity Entity;
|
||||
|
||||
public readonly bool Remove = false;
|
||||
|
||||
public SpawnOrRemove(Entity entity, bool remove)
|
||||
{
|
||||
Entity = entity;
|
||||
Remove = remove;
|
||||
}
|
||||
}
|
||||
|
||||
public EntitySpawner()
|
||||
: base(null)
|
||||
{
|
||||
spawnQueue = new Queue<IEntitySpawnInfo>();
|
||||
removeQueue = new Queue<Entity>();
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory));
|
||||
}
|
||||
|
||||
public void AddToRemoveQueue(Entity entity)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (removeQueue.Contains(entity) || entity.Removed) return;
|
||||
|
||||
removeQueue.Enqueue(entity);
|
||||
}
|
||||
|
||||
public void AddToRemoveQueue(Item item)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (removeQueue.Contains(item) || item.Removed) return;
|
||||
|
||||
removeQueue.Enqueue(item);
|
||||
if (item.ContainedItems == null) return;
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
if (containedItem != null) AddToRemoveQueue(containedItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateNetworkEvent(Entity entity, bool remove)
|
||||
{
|
||||
if (GameMain.Server != null && entity != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
while (spawnQueue.Count>0)
|
||||
{
|
||||
var entitySpawnInfo = spawnQueue.Dequeue();
|
||||
|
||||
var spawnedEntity = entitySpawnInfo.Spawn();
|
||||
if (spawnedEntity != null)
|
||||
{
|
||||
CreateNetworkEvent(spawnedEntity, false);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeQueue.Count > 0)
|
||||
{
|
||||
var removedEntity = removeQueue.Dequeue();
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CreateNetworkEvent(removedEntity, true);
|
||||
}
|
||||
|
||||
removedEntity.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer message, Client client, object[] extraData = null)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
|
||||
|
||||
message.Write(entities.Remove);
|
||||
|
||||
if (entities.Remove)
|
||||
{
|
||||
message.Write(entities.Entity.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entities.Entity is Item)
|
||||
{
|
||||
message.Write((byte)SpawnableType.Item);
|
||||
((Item)entities.Entity).WriteSpawnData(message);
|
||||
}
|
||||
else if (entities.Entity is Character)
|
||||
{
|
||||
message.Write((byte)SpawnableType.Character);
|
||||
DebugConsole.NewMessage("WRITING CHARACTER DATA: " + (entities.Entity).ToString(),Color.Cyan);
|
||||
((Character)entities.Entity).WriteSpawnData(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum FileTransferStatus
|
||||
{
|
||||
NotStarted, Sending, Receiving, Finished, Canceled, Error
|
||||
}
|
||||
|
||||
enum FileTransferMessageType
|
||||
{
|
||||
Unknown, Initiate, Data, Cancel
|
||||
}
|
||||
|
||||
enum FileTransferType
|
||||
{
|
||||
Submarine
|
||||
}
|
||||
|
||||
class FileSender
|
||||
{
|
||||
public class FileTransferOut
|
||||
{
|
||||
private byte[] data;
|
||||
|
||||
private DateTime startingTime;
|
||||
|
||||
private NetConnection connection;
|
||||
|
||||
public FileTransferStatus Status;
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public FileTransferType FileType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float Progress
|
||||
{
|
||||
get { return SentOffset / (float)Data.Length; }
|
||||
}
|
||||
|
||||
public float WaitTimer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public byte[] Data
|
||||
{
|
||||
get { return data; }
|
||||
}
|
||||
|
||||
public int SentOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public NetConnection Connection
|
||||
{
|
||||
get { return connection; }
|
||||
}
|
||||
|
||||
public int SequenceChannel;
|
||||
|
||||
public FileTransferOut(NetConnection recipient, FileTransferType fileType, string filePath)
|
||||
{
|
||||
connection = recipient;
|
||||
|
||||
FileType = fileType;
|
||||
FilePath = filePath;
|
||||
FileName = Path.GetFileName(filePath);
|
||||
|
||||
Status = FileTransferStatus.NotStarted;
|
||||
|
||||
startingTime = DateTime.Now;
|
||||
|
||||
data = File.ReadAllBytes(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
const int MaxTransferCount = 16;
|
||||
const int MaxTransferCountPerRecipient = 5;
|
||||
|
||||
public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0);
|
||||
|
||||
public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
|
||||
public FileTransferDelegate OnStarted;
|
||||
public FileTransferDelegate OnEnded;
|
||||
|
||||
private List<FileTransferOut> activeTransfers;
|
||||
|
||||
private int chunkLen;
|
||||
|
||||
private NetPeer peer;
|
||||
|
||||
public List<FileTransferOut> ActiveTransfers
|
||||
{
|
||||
get { return activeTransfers; }
|
||||
}
|
||||
|
||||
public FileSender(NetworkMember networkMember)
|
||||
{
|
||||
peer = networkMember.netPeer;
|
||||
chunkLen = peer.Configuration.MaximumTransmissionUnit - 100;
|
||||
|
||||
activeTransfers = new List<FileTransferOut>();
|
||||
}
|
||||
|
||||
public FileTransferOut StartTransfer(NetConnection recipient, FileTransferType fileType, string filePath)
|
||||
{
|
||||
if (activeTransfers.Count >= MaxTransferCount)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (activeTransfers.Count(t => t.Connection == recipient) > MaxTransferCountPerRecipient)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initiate file transfer (file \""+filePath+"\" not found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
FileTransferOut transfer = null;
|
||||
try
|
||||
{
|
||||
transfer = new FileTransferOut(recipient, fileType, filePath);
|
||||
transfer.SequenceChannel = 1;
|
||||
while (activeTransfers.Any(t => t.Connection == recipient && t.SequenceChannel == transfer.SequenceChannel))
|
||||
{
|
||||
transfer.SequenceChannel++;
|
||||
}
|
||||
activeTransfers.Add(transfer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initiate file transfer", e);
|
||||
}
|
||||
|
||||
OnStarted(transfer);
|
||||
|
||||
return transfer;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
activeTransfers.RemoveAll(t => t.Connection.Status != NetConnectionStatus.Connected);
|
||||
|
||||
var endedTransfers = activeTransfers.FindAll(t =>
|
||||
t.Connection.Status != NetConnectionStatus.Connected ||
|
||||
t.Status == FileTransferStatus.Finished ||
|
||||
t.Status == FileTransferStatus.Canceled ||
|
||||
t.Status == FileTransferStatus.Error);
|
||||
|
||||
foreach (FileTransferOut transfer in endedTransfers)
|
||||
{
|
||||
activeTransfers.Remove(transfer);
|
||||
OnEnded(transfer);
|
||||
}
|
||||
|
||||
foreach (FileTransferOut transfer in activeTransfers)
|
||||
{
|
||||
transfer.WaitTimer -= deltaTime;
|
||||
if (transfer.WaitTimer > 0.0f) continue;
|
||||
|
||||
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue;
|
||||
|
||||
transfer.WaitTimer = transfer.Connection.AverageRoundtripTime;
|
||||
|
||||
// send another part of the file
|
||||
long remaining = transfer.Data.Length - transfer.SentOffset;
|
||||
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
|
||||
|
||||
NetOutgoingMessage message;
|
||||
|
||||
//first message; send length, chunk length, file name etc
|
||||
if (transfer.SentOffset == 0)
|
||||
{
|
||||
message = peer.CreateMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Initiate);
|
||||
message.Write((byte)transfer.FileType);
|
||||
message.Write((ushort)chunkLen);
|
||||
message.Write((ulong)transfer.Data.Length);
|
||||
message.Write(transfer.FileName);
|
||||
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
|
||||
|
||||
transfer.Status = FileTransferStatus.Sending;
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending file transfer initiation message: ");
|
||||
DebugConsole.Log(" File: " + transfer.FileName);
|
||||
DebugConsole.Log(" Size: " + transfer.Data.Length);
|
||||
DebugConsole.Log(" Sequence channel: " + transfer.SequenceChannel);
|
||||
}
|
||||
}
|
||||
|
||||
message = peer.CreateMessage(1 + 1 + sendByteCount);
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Data);
|
||||
|
||||
byte[] sendBytes = new byte[sendByteCount];
|
||||
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
|
||||
|
||||
message.Write(sendBytes);
|
||||
|
||||
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
|
||||
transfer.SentOffset += sendByteCount;
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
|
||||
}
|
||||
|
||||
if (remaining - sendByteCount <= 0)
|
||||
{
|
||||
transfer.Status = FileTransferStatus.Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelTransfer(FileTransferOut transfer)
|
||||
{
|
||||
transfer.Status = FileTransferStatus.Canceled;
|
||||
activeTransfers.Remove(transfer);
|
||||
|
||||
OnEnded(transfer);
|
||||
|
||||
GameMain.Server.SendCancelTransferMsg(transfer);
|
||||
}
|
||||
|
||||
public void ReadFileRequest(NetIncomingMessage inc)
|
||||
{
|
||||
byte messageType = inc.ReadByte();
|
||||
|
||||
if (messageType == (byte)FileTransferMessageType.Cancel)
|
||||
{
|
||||
byte sequenceChannel = inc.ReadByte();
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
|
||||
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
byte fileType = inc.ReadByte();
|
||||
switch (fileType)
|
||||
{
|
||||
case (byte)FileTransferType.Submarine:
|
||||
string fileName = inc.ReadString();
|
||||
string fileHash = inc.ReadString();
|
||||
var requestedSubmarine = Submarine.SavedSubmarines.Find(s => s.Name == fileName && s.MD5Hash.Hash == fileHash);
|
||||
|
||||
if (requestedSubmarine != null)
|
||||
{
|
||||
StartTransfer(inc.SenderConnection, FileTransferType.Submarine, requestedSubmarine.FilePath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,241 @@
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class UnauthenticatedClient
|
||||
{
|
||||
public NetConnection Connection;
|
||||
public int Nonce;
|
||||
|
||||
public int failedAttempts;
|
||||
|
||||
public float AuthTimer;
|
||||
|
||||
public UnauthenticatedClient(NetConnection connection, int nonce)
|
||||
{
|
||||
Connection = connection;
|
||||
Nonce = nonce;
|
||||
|
||||
AuthTimer = 10.0f;
|
||||
|
||||
failedAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
partial class GameServer : NetworkMember, IPropertyObject
|
||||
{
|
||||
List<UnauthenticatedClient> unauthenticatedClients = new List<UnauthenticatedClient>();
|
||||
|
||||
private void ClientAuthRequest(NetConnection conn)
|
||||
{
|
||||
//client wants to know if server requires password
|
||||
if (ConnectedClients.Find(c => c.Connection == conn) != null)
|
||||
{
|
||||
//this client has already been authenticated
|
||||
return;
|
||||
}
|
||||
|
||||
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == conn);
|
||||
if (unauthClient == null)
|
||||
{
|
||||
//new client, generate nonce and add to unauth queue
|
||||
if (ConnectedClients.Count >= MaxPlayers)
|
||||
{
|
||||
//server is full, can't allow new connection
|
||||
conn.Disconnect("Server full");
|
||||
return;
|
||||
}
|
||||
|
||||
int nonce = CryptoRandom.Instance.Next();
|
||||
unauthClient = new UnauthenticatedClient(conn, nonce);
|
||||
unauthenticatedClients.Add(unauthClient);
|
||||
}
|
||||
unauthClient.AuthTimer = 10.0f;
|
||||
//if the client is already in the queue, getting another unauth request means that our response was lost; resend
|
||||
NetOutgoingMessage nonceMsg = server.CreateMessage();
|
||||
nonceMsg.Write((byte)ServerPacketHeader.AUTH_RESPONSE);
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
nonceMsg.Write(false); //false = no password
|
||||
}
|
||||
else
|
||||
{
|
||||
nonceMsg.Write(true); //true = password
|
||||
nonceMsg.Write((Int32)unauthClient.Nonce); //here's nonce, encrypt with this
|
||||
}
|
||||
server.SendMessage(nonceMsg, conn, NetDeliveryMethod.Unreliable);
|
||||
}
|
||||
|
||||
private void ClientInitRequest(NetIncomingMessage inc)
|
||||
{
|
||||
if (ConnectedClients.Find(c => c.Connection == inc.SenderConnection) != null)
|
||||
{
|
||||
//this client was already authenticated
|
||||
//another init request means they didn't get any update packets yet
|
||||
return;
|
||||
}
|
||||
|
||||
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == inc.SenderConnection);
|
||||
if (unauthClient == null)
|
||||
{
|
||||
//client did not ask for nonce first, can't authorize
|
||||
inc.SenderConnection.Disconnect("Client did not properly request authentication.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
//decrypt message and compare password
|
||||
string saltedPw = password;
|
||||
saltedPw = saltedPw + Convert.ToString(unauthClient.Nonce);
|
||||
saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(saltedPw)));
|
||||
string clPw = inc.ReadString();
|
||||
if (clPw != saltedPw)
|
||||
{
|
||||
unauthClient.failedAttempts++;
|
||||
if (unauthClient.failedAttempts > 3)
|
||||
{
|
||||
//disconnect and ban after too many failed attempts
|
||||
banList.BanPlayer("Unnamed", unauthClient.Connection.RemoteEndPoint.Address.ToString());
|
||||
DisconnectUnauthClient(inc, unauthClient, "Too many failed login attempts. You have been automatically banned from the server.");
|
||||
|
||||
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", Color.Red);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//not disconnecting the player here, because they'll still use the same connection and nonce if they try logging in again
|
||||
NetOutgoingMessage reject = server.CreateMessage();
|
||||
reject.Write((byte)ServerPacketHeader.AUTH_FAILURE);
|
||||
reject.Write("Wrong password! You have "+Convert.ToString(4-unauthClient.failedAttempts)+" more attempts before you're banned from the server.");
|
||||
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", Color.Red);
|
||||
server.SendMessage(reject, unauthClient.Connection, NetDeliveryMethod.Unreliable);
|
||||
unauthClient.AuthTimer = 10.0f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
string clVersion = inc.ReadString();
|
||||
string clPackageName = inc.ReadString();
|
||||
string clPackageHash = inc.ReadString();
|
||||
|
||||
string clName = Client.SanitizeName(inc.ReadString());
|
||||
if (string.IsNullOrWhiteSpace(clName))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "You need a name.");
|
||||
|
||||
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clVersion != GameMain.Version.ToString())
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "Version " + GameMain.Version + " required to connect to the server (Your version: " + clVersion + ")");
|
||||
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", Color.Red);
|
||||
return;
|
||||
}
|
||||
if (clPackageName != GameMain.SelectedPackage.Name)
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "Your content package (" + clPackageName + ") doesn't match the server's version (" + GameMain.SelectedPackage.Name + ")");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong content package name)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong content package name)", Color.Red);
|
||||
return;
|
||||
}
|
||||
if (clPackageHash != GameMain.SelectedPackage.MD5hash.Hash)
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "Your content package (MD5: " + clPackageHash + ") doesn't match the server's version (MD5: " + GameMain.SelectedPackage.MD5hash.Hash + ")");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong content package hash)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong content package hash)", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!whitelist.IsWhiteListed(clName, inc.SenderConnection.RemoteEndPoint.Address.ToString()))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "You're not in this server's whitelist.");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", Color.Red);
|
||||
return;
|
||||
}
|
||||
if (!Client.IsValidName(clName))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "Your name contains illegal symbols.");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", Color.Red);
|
||||
return;
|
||||
}
|
||||
if (clName.ToLower() == Name.ToLower())
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, "That name is taken.");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", Color.Red);
|
||||
return;
|
||||
}
|
||||
Client nameTaken = ConnectedClients.Find(c => c.name.ToLower() == clName.ToLower());
|
||||
if (nameTaken != null)
|
||||
{
|
||||
if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString())
|
||||
{
|
||||
//both name and IP address match, replace this player's connection
|
||||
nameTaken.Connection.Disconnect("Your session was taken by a new connection on the same IP address.");
|
||||
nameTaken.Connection = unauthClient.Connection;
|
||||
nameTaken.InitClientSync(); //reinitialize sync ids because this is a new connection
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
unauthClient = null;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//can't authorize this client
|
||||
DisconnectUnauthClient(inc, unauthClient, "That name is taken.");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", Color.Red);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//new client
|
||||
Client newClient = new Client(clName, GetNewClientID());
|
||||
newClient.InitClientSync();
|
||||
newClient.Connection = unauthClient.Connection;
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
unauthClient = null;
|
||||
ConnectedClients.Add(newClient);
|
||||
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.AddPlayer(newClient.name);
|
||||
#endif
|
||||
GameMain.Server.SendChatMessage(clName + " has joined the server.", ChatMessageType.Server, null);
|
||||
|
||||
var savedPermissions = clientPermissions.Find(cp => cp.IP == newClient.Connection.RemoteEndPoint.Address.ToString());
|
||||
if (savedPermissions != null)
|
||||
{
|
||||
newClient.SetPermissions(savedPermissions.Permissions);
|
||||
}
|
||||
else
|
||||
{
|
||||
newClient.SetPermissions(ClientPermissions.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, string reason)
|
||||
{
|
||||
inc.SenderConnection.Disconnect(reason);
|
||||
|
||||
if (unauthClient != null)
|
||||
{
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum SelectionMode
|
||||
{
|
||||
Manual = 0, Random = 1, Vote = 2
|
||||
}
|
||||
|
||||
enum YesNoMaybe
|
||||
{
|
||||
No = 0, Maybe = 1, Yes = 2
|
||||
}
|
||||
|
||||
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<string, ObjectProperty> ObjectProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Dictionary<string, int> extraCargo;
|
||||
|
||||
public bool ShowNetStats;
|
||||
|
||||
private TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 30);
|
||||
private TimeSpan sparseUpdateInterval = new TimeSpan(0, 0, 0, 3);
|
||||
|
||||
private SelectionMode subSelectionMode, modeSelectionMode;
|
||||
|
||||
private bool registeredToMaster;
|
||||
|
||||
private WhiteList whitelist;
|
||||
private BanList banList;
|
||||
|
||||
private string password;
|
||||
|
||||
private string adminAuthPass = "";
|
||||
public string AdminAuthPass
|
||||
{
|
||||
set
|
||||
{
|
||||
DebugConsole.NewMessage("Admin auth pass changed!",Color.Yellow);
|
||||
adminAuthPass = "";
|
||||
if (value.Length > 0)
|
||||
{
|
||||
adminAuthPass = Encoding.UTF8.GetString(Lidgren.Network.NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float AutoRestartTimer;
|
||||
|
||||
private bool autoRestart;
|
||||
|
||||
private List<SavedClientPermission> clientPermissions = new List<SavedClientPermission>();
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool RandomizeSeed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
[HasDefaultValue(300.0f, true)]
|
||||
public float RespawnInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(180.0f, true)]
|
||||
public float MaxTransportTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.2f, true)]
|
||||
public float MinRespawnRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
[HasDefaultValue(60.0f, true)]
|
||||
public float AutoRestartInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool AllowSpectating
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool EndRoundAtLevelEnd
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool SaveServerLogs
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool AllowFileTransfers
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(800, true)]
|
||||
private int LinesPerLogFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return log.LinesPerFile;
|
||||
}
|
||||
set
|
||||
{
|
||||
log.LinesPerFile = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoRestart
|
||||
{
|
||||
get { return (connectedClients.Count != 0) && autoRestart; }
|
||||
set
|
||||
{
|
||||
autoRestart = value;
|
||||
|
||||
AutoRestartTimer = autoRestart ? AutoRestartInterval : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public YesNoMaybe TraitorsEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool AllowRespawn
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public SelectionMode SubSelectionMode
|
||||
{
|
||||
get { return subSelectionMode; }
|
||||
}
|
||||
|
||||
public SelectionMode ModeSelectionMode
|
||||
{
|
||||
get { return modeSelectionMode; }
|
||||
}
|
||||
|
||||
public BanList BanList
|
||||
{
|
||||
get { return banList; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, true)]
|
||||
public bool AllowVoteKick
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.6f, true)]
|
||||
public float EndVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.6f, true)]
|
||||
public float KickVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
XDocument doc = new XDocument(new XElement("serversettings"));
|
||||
|
||||
ObjectProperty.SaveProperties(this, doc.Root, true);
|
||||
|
||||
doc.Root.SetAttributeValue("SubSelection", subSelectionMode.ToString());
|
||||
doc.Root.SetAttributeValue("ModeSelection", modeSelectionMode.ToString());
|
||||
|
||||
doc.Root.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.ToString());
|
||||
|
||||
if (GameMain.NetLobbyScreen != null
|
||||
#if CLIENT
|
||||
&& GameMain.NetLobbyScreen.ServerMessage != null
|
||||
#endif
|
||||
)
|
||||
{
|
||||
doc.Root.SetAttributeValue("ServerMessage", GameMain.NetLobbyScreen.ServerMessageText);
|
||||
}
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.Indent = true;
|
||||
settings.NewLineOnAttributes = true;
|
||||
|
||||
using (var writer = XmlWriter.Create(SettingsFile, settings))
|
||||
{
|
||||
doc.Save(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
XDocument doc = null;
|
||||
if (File.Exists(SettingsFile))
|
||||
{
|
||||
doc = ToolBox.TryLoadXml(SettingsFile);
|
||||
}
|
||||
|
||||
if (doc == null || doc.Root == null)
|
||||
{
|
||||
doc = new XDocument(new XElement("serversettings"));
|
||||
}
|
||||
|
||||
ObjectProperties = ObjectProperty.InitProperties(this, doc.Root);
|
||||
|
||||
subSelectionMode = SelectionMode.Manual;
|
||||
Enum.TryParse<SelectionMode>(ToolBox.GetAttributeString(doc.Root, "SubSelection", "Manual"), out subSelectionMode);
|
||||
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
|
||||
|
||||
modeSelectionMode = SelectionMode.Manual;
|
||||
Enum.TryParse<SelectionMode>(ToolBox.GetAttributeString(doc.Root, "ModeSelection", "Manual"), out modeSelectionMode);
|
||||
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
|
||||
|
||||
var traitorsEnabled = TraitorsEnabled;
|
||||
Enum.TryParse<YesNoMaybe>(ToolBox.GetAttributeString(doc.Root, "TraitorsEnabled", "No"), out traitorsEnabled);
|
||||
TraitorsEnabled = traitorsEnabled;
|
||||
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
|
||||
|
||||
if (GameMain.NetLobbyScreen != null
|
||||
#if CLIENT
|
||||
&& GameMain.NetLobbyScreen.ServerMessage != null
|
||||
#endif
|
||||
)
|
||||
{
|
||||
GameMain.NetLobbyScreen.ServerMessageText = ToolBox.GetAttributeString(doc.Root, "ServerMessage", "");
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
showLogButton.Visible = SaveServerLogs;
|
||||
#endif
|
||||
|
||||
List<string> monsterNames = Directory.GetDirectories("Content/Characters").ToList();
|
||||
for (int i = 0; i < monsterNames.Count; i++)
|
||||
{
|
||||
monsterNames[i] = monsterNames[i].Replace("Content/Characters", "").Replace("/", "").Replace("\\", "");
|
||||
}
|
||||
monsterEnabled = new Dictionary<string, bool>();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
monsterEnabled.Add(s, true);
|
||||
}
|
||||
extraCargo = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
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<ClientPermissions>(separatedLine.Last(), out permissions))
|
||||
{
|
||||
clientPermissions.Add(new SavedClientPermission(name, ip, permissions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveClientPermissions()
|
||||
{
|
||||
GameServer.Log("Saving client permissions", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
interface INetSerializable { }
|
||||
|
||||
/// <summary>
|
||||
/// Interface for entities that the clients can send information of to the server
|
||||
/// </summary>
|
||||
interface IClientSerializable : INetSerializable
|
||||
{
|
||||
#if CLIENT
|
||||
void ClientWrite(NetBuffer msg, object[] extraData = null);
|
||||
#endif
|
||||
void ServerRead(ClientNetObject type, NetBuffer msg, Client c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for entities that the server can send information of to the clients
|
||||
/// </summary>
|
||||
interface IServerSerializable : INetSerializable
|
||||
{
|
||||
void ServerWrite(NetBuffer msg, Client c, object[] extraData = null);
|
||||
#if CLIENT
|
||||
void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetBufferExtensions
|
||||
{
|
||||
//public static void WriteEnum(this NetBuffer buffer, Enum value)
|
||||
//{
|
||||
// buffer.WriteRangedInteger(0, Enum.GetValues(value.GetType()).Length - 1, Convert.ToInt32(value));
|
||||
//}
|
||||
|
||||
//public static TEnum ReadEnum<TEnum>(this NetBuffer buffer)
|
||||
//{
|
||||
// return (TEnum)(object)buffer.ReadRangedInteger(0, Enum.GetValues(typeof(TEnum)).Length - 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetConfig
|
||||
{
|
||||
public const int DefaultPort = 14242;
|
||||
|
||||
//UpdateEntity networkevents aren't sent to clients if they're further than this from the entity
|
||||
public const float UpdateEntityDistance = 2500.0f;
|
||||
|
||||
public const int MaxPlayers = 16;
|
||||
|
||||
public static string MasterServerUrl = GameMain.Config.MasterServerUrl;
|
||||
|
||||
//if a Character is further than this from the sub, the server will ignore it
|
||||
//(in display units)
|
||||
public const float CharacterIgnoreDistance = 20000.0f;
|
||||
|
||||
//how much the physics body of an item has to move until the server
|
||||
//send a position update to clients (in sim units)
|
||||
public const float ItemPosUpdateDistance = 2.0f;
|
||||
|
||||
public const float LargeCharacterUpdateInterval = 5.0f;
|
||||
|
||||
public const float DeleteDisconnectedTime = 10.0f;
|
||||
|
||||
public const float IdSendInterval = 0.2f;
|
||||
public const float RerequestInterval = 0.2f;
|
||||
|
||||
public const int ReliableMessageBufferSize = 500;
|
||||
public const int ResendAttempts = 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class NetEntityEvent
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
ComponentState,
|
||||
InventoryState,
|
||||
Status,
|
||||
Repair,
|
||||
ApplyStatusEffect,
|
||||
ChangeProperty,
|
||||
Control
|
||||
}
|
||||
|
||||
public readonly Entity Entity;
|
||||
public readonly UInt16 ID;
|
||||
|
||||
//arbitrary extra data that will be passed to the Write method of the serializable entity
|
||||
//(the index of an itemcomponent for example)
|
||||
protected object[] Data;
|
||||
|
||||
protected NetEntityEvent(INetSerializable entity, UInt16 id)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Entity = entity as Entity;
|
||||
}
|
||||
|
||||
public void SetData(object[] data)
|
||||
{
|
||||
this.Data = data;
|
||||
}
|
||||
|
||||
public bool IsDuplicate(NetEntityEvent other)
|
||||
{
|
||||
if (other.Entity != this.Entity) return false;
|
||||
|
||||
if (Data != null && other.Data != null)
|
||||
{
|
||||
if (Data.Length == other.Data.Length)
|
||||
{
|
||||
for (int i = 0; i<Data.Length; i++)
|
||||
{
|
||||
if (!Data[i].Equals(other.Data[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return Data == other.Data;
|
||||
}
|
||||
}
|
||||
|
||||
class ServerEntityEvent : NetEntityEvent
|
||||
{
|
||||
private IServerSerializable serializable;
|
||||
|
||||
public bool Sent;
|
||||
|
||||
#if DEBUG
|
||||
public string StackTrace;
|
||||
#endif
|
||||
|
||||
private double createTime;
|
||||
public double CreateTime
|
||||
{
|
||||
get { return createTime; }
|
||||
}
|
||||
|
||||
public ServerEntityEvent(IServerSerializable entity, UInt16 id)
|
||||
: base(entity, id)
|
||||
{
|
||||
serializable = entity;
|
||||
|
||||
createTime = Timing.TotalTime;
|
||||
|
||||
#if DEBUG
|
||||
StackTrace = Environment.StackTrace.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Write(NetBuffer msg, Client recipient)
|
||||
{
|
||||
serializable.ServerWrite(msg, recipient, Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class NetEntityEventManager
|
||||
{
|
||||
public const int MaxEventBufferLength = 1024;
|
||||
public const int MaxEventsPerWrite = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager
|
||||
/// </summary>
|
||||
protected void Write(NetOutgoingMessage msg, List<NetEntityEvent> eventsToSync, Client recipient = null)
|
||||
{
|
||||
//write into a temporary buffer so we can write the number of events before the actual data
|
||||
NetBuffer tempBuffer = new NetBuffer();
|
||||
|
||||
int eventCount = 0;
|
||||
foreach (NetEntityEvent e in eventsToSync)
|
||||
{
|
||||
//write into a temporary buffer so we can write the length before the actual data
|
||||
NetBuffer tempEventBuffer = new NetBuffer();
|
||||
try
|
||||
{
|
||||
WriteEvent(tempEventBuffer, e, recipient);
|
||||
}
|
||||
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception);
|
||||
|
||||
//write an empty event to avoid messing up IDs
|
||||
//(otherwise the clients might read the next event in the message and think its ID
|
||||
//is consecutive to the previous one, even though we skipped over this broken event)
|
||||
tempBuffer.Write((UInt16)0);
|
||||
tempBuffer.WritePadBits();
|
||||
eventCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > NetPeerConfiguration.kDefaultMTU - 20)
|
||||
{
|
||||
//no more room in this packet
|
||||
break;
|
||||
}
|
||||
|
||||
//the ID has been taken by another entity (the original entity has been removed) -> write an empty event
|
||||
if (Entity.FindEntityByID(e.Entity.ID) != e.Entity)
|
||||
{
|
||||
//technically the clients don't have any use for these, but removing events and shifting the IDs of all
|
||||
//consecutive ones is so error-prone that I think this is a safer option
|
||||
tempBuffer.Write((UInt16)0);
|
||||
tempBuffer.WritePadBits();
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write((UInt16)e.Entity.ID);
|
||||
tempBuffer.Write((byte)tempEventBuffer.LengthBytes);
|
||||
tempBuffer.Write(tempEventBuffer);
|
||||
tempBuffer.WritePadBits();
|
||||
}
|
||||
|
||||
eventCount++;
|
||||
}
|
||||
|
||||
if (eventCount > 0)
|
||||
{
|
||||
msg.Write(eventsToSync[0].ID);
|
||||
msg.Write((byte)eventCount);
|
||||
msg.Write(tempBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerEntityEventManager : NetEntityEventManager
|
||||
{
|
||||
private List<ServerEntityEvent> events;
|
||||
|
||||
//list of unique events (i.e. !IsDuplicate) created during the round
|
||||
//used for syncing clients who join mid-round
|
||||
private List<ServerEntityEvent> uniqueEvents;
|
||||
|
||||
private UInt16 lastSentToAll;
|
||||
|
||||
public List<ServerEntityEvent> Events
|
||||
{
|
||||
get { return events; }
|
||||
}
|
||||
|
||||
public List<ServerEntityEvent> UniqueEvents
|
||||
{
|
||||
get { return uniqueEvents; }
|
||||
}
|
||||
|
||||
private class BufferedEvent
|
||||
{
|
||||
public readonly Client Sender;
|
||||
|
||||
public readonly UInt16 CharacterStateID;
|
||||
public readonly NetBuffer Data;
|
||||
|
||||
public readonly Character Character;
|
||||
|
||||
public readonly IClientSerializable TargetEntity;
|
||||
|
||||
public bool IsProcessed;
|
||||
|
||||
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, NetBuffer data)
|
||||
{
|
||||
this.Sender = sender;
|
||||
this.Character = senderCharacter;
|
||||
this.CharacterStateID = characterStateID;
|
||||
|
||||
this.TargetEntity = targetEntity;
|
||||
|
||||
this.Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private List<BufferedEvent> bufferedEvents;
|
||||
|
||||
private UInt16 ID;
|
||||
|
||||
private GameServer server;
|
||||
|
||||
public ServerEntityEventManager(GameServer server)
|
||||
{
|
||||
events = new List<ServerEntityEvent>();
|
||||
|
||||
this.server = server;
|
||||
|
||||
bufferedEvents = new List<BufferedEvent>();
|
||||
|
||||
uniqueEvents = new List<ServerEntityEvent>();
|
||||
}
|
||||
|
||||
public void CreateEvent(IServerSerializable entity, object[] extraData = null)
|
||||
{
|
||||
if (entity == null || !(entity is Entity))
|
||||
{
|
||||
DebugConsole.ThrowError("Can't create an entity event for " + entity + "!");
|
||||
return;
|
||||
}
|
||||
|
||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||
if (extraData != null) newEvent.SetData(extraData);
|
||||
|
||||
//remove events that have been sent to all clients, they are redundant now
|
||||
//keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
events.RemoveAll(e => NetIdUtils.IdMoreRecent(lastSentToAll, e.ID));
|
||||
|
||||
if (server.ConnectedClients.Count(c => c.inGame) == 0 && events.Count > 1)
|
||||
{
|
||||
events.RemoveRange(0, events.Count - 1);
|
||||
}
|
||||
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
{
|
||||
//we already have an identical event that's waiting to be sent
|
||||
// -> no need to add a new one
|
||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return;
|
||||
}
|
||||
|
||||
ID++;
|
||||
|
||||
events.Add(newEvent);
|
||||
|
||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||
{
|
||||
//create a copy of the event and give it a new ID
|
||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||
uniqueEvent.SetData(extraData);
|
||||
|
||||
uniqueEvents.Add(uniqueEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(List<Client> clients)
|
||||
{
|
||||
foreach (BufferedEvent bufferedEvent in bufferedEvents)
|
||||
{
|
||||
if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead)
|
||||
{
|
||||
bufferedEvent.IsProcessed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
//delay reading the events until the inputs for the corresponding frame have been processed
|
||||
|
||||
//UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored)
|
||||
//atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored
|
||||
if (!bufferedEvent.Character.IsUnconscious &&
|
||||
NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to read event for entity \"" + bufferedEvent.TargetEntity.ToString() + "\"!", e);
|
||||
}
|
||||
}
|
||||
|
||||
bufferedEvent.IsProcessed = true;
|
||||
}
|
||||
|
||||
var inGameClients = clients.FindAll(c => c.inGame && !c.NeedsMidRoundSync);
|
||||
if (inGameClients.Count > 0)
|
||||
{
|
||||
lastSentToAll = inGameClients[0].lastRecvEntityEventID;
|
||||
inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.lastRecvEntityEventID)) lastSentToAll = c.lastRecvEntityEventID; });
|
||||
|
||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
if (firstEventToResend != null && (Timing.TotalTime - firstEventToResend.CreateTime) > 10.0f)
|
||||
{
|
||||
//it's been 10 seconds since this event was created
|
||||
//kick everyone that hasn't received it yet, this is way too old
|
||||
List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.lastRecvEntityEventID));
|
||||
toKick.ForEach(c =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.name + " was kicked due to excessive desync (expected old event " + c.lastRecvEntityEventID.ToString() + ")", Microsoft.Xna.Framework.Color.Red);
|
||||
server.DisconnectClient(c, "", "You have been disconnected because of excessive desync");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (events.Count > 0)
|
||||
{
|
||||
//the client is waiting for an event that we don't have anymore
|
||||
//(the ID they're expecting is smaller than the ID of the first event in our list)
|
||||
List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.lastRecvEntityEventID + 1)));
|
||||
toKick.ForEach(c =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.name + " was kicked due to excessive desync (expected " + c.lastRecvEntityEventID.ToString() + ", last available is " + events[0].ID.ToString() + ")", Microsoft.Xna.Framework.Color.Red);
|
||||
server.DisconnectClient(c, "", "You have been disconnected because of excessive desync");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var timedOutClients = clients.FindAll(c => c.inGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
|
||||
timedOutClients.ForEach(c => GameMain.Server.DisconnectClient(c, "", "You have been disconnected because syncing your client with the server took too long."));
|
||||
|
||||
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
||||
}
|
||||
|
||||
private void BufferEvent(BufferedEvent bufferedEvent)
|
||||
{
|
||||
if (bufferedEvents.Count > 512)
|
||||
{
|
||||
//should normally never happen
|
||||
|
||||
//a client could potentially spam events with a much higher character state ID
|
||||
//than the state of their character and/or stop sending character inputs,
|
||||
//so we'll drop some events to make sure no-one blows up our buffer
|
||||
bufferedEvents.RemoveRange(0, 256);
|
||||
}
|
||||
|
||||
bufferedEvents.Add(bufferedEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all the events that the client hasn't received yet into the outgoing message
|
||||
/// </summary>
|
||||
public void Write(Client client, NetOutgoingMessage msg)
|
||||
{
|
||||
List<NetEntityEvent> eventsToSync = null;
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
eventsToSync = GetEventsToSync(client, uniqueEvents);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventsToSync = GetEventsToSync(client, events);
|
||||
}
|
||||
|
||||
if (eventsToSync.Count == 0) return;
|
||||
|
||||
//too many events for one packet
|
||||
if (eventsToSync.Count > MaxEventsPerWrite)
|
||||
{
|
||||
eventsToSync.RemoveRange(MaxEventsPerWrite, eventsToSync.Count - MaxEventsPerWrite);
|
||||
}
|
||||
|
||||
foreach (NetEntityEvent entityEvent in eventsToSync)
|
||||
{
|
||||
(entityEvent as ServerEntityEvent).Sent = true;
|
||||
client.entityEventLastSent[entityEvent.ID] = (float)NetTime.Now;
|
||||
}
|
||||
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
|
||||
|
||||
//how many (unique) events the clients had missed before joining
|
||||
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
|
||||
//ID of the first event sent after the client joined
|
||||
//(after the client has been synced they'll switch their lastReceivedID
|
||||
//to the one before this, and the eventmanagers will start to function "normally")
|
||||
client.FirstNewEventID = events.Count == 0 ? (UInt16)0 : events[events.Count - 1].ID;
|
||||
|
||||
msg.Write(client.UnreceivedEntityEventCount);
|
||||
msg.Write(client.FirstNewEventID);
|
||||
//DebugConsole.NewMessage(eventsToSync[0].ID.ToString(), Microsoft.Xna.Framework.Color.Yellow);
|
||||
Write(msg, eventsToSync, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.ENTITY_EVENT);
|
||||
Write(msg, eventsToSync, client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of events that should be sent to the client from the eventList
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="eventList"></param>
|
||||
/// <returns></returns>
|
||||
private List<NetEntityEvent> GetEventsToSync(Client client, List<ServerEntityEvent> eventList)
|
||||
{
|
||||
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
|
||||
if (eventList.Count == 0) return eventsToSync;
|
||||
|
||||
//find the index of the first event the client hasn't received
|
||||
int startIndex = eventList.Count;
|
||||
while (startIndex > 0 &&
|
||||
NetIdUtils.IdMoreRecent(eventList[startIndex - 1].ID,client.lastRecvEntityEventID))
|
||||
{
|
||||
startIndex--;
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < eventList.Count; i++)
|
||||
{
|
||||
//find the first event that hasn't been sent in 1.5 * roundtriptime or at all
|
||||
float lastSent = 0;
|
||||
client.entityEventLastSent.TryGetValue(eventList[i].ID, out lastSent);
|
||||
|
||||
if (lastSent > NetTime.Now - client.Connection.AverageRoundtripTime * 1.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
eventsToSync.AddRange(eventList.GetRange(i, eventList.Count - i));
|
||||
break;
|
||||
}
|
||||
|
||||
return eventsToSync;
|
||||
}
|
||||
|
||||
public void InitClientMidRoundSync(Client client)
|
||||
{
|
||||
//no need for midround syncing if no events have been created,
|
||||
//or if the first created unique event is still in the event list
|
||||
if (uniqueEvents.Count == 0 || (events.Count > 0 && events[0].ID == uniqueEvents[0].ID))
|
||||
{
|
||||
client.UnreceivedEntityEventCount = 0;
|
||||
client.FirstNewEventID = 0;
|
||||
client.NeedsMidRoundSync = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds;
|
||||
midRoundSyncTimeOut = Math.Max(5.0f, midRoundSyncTimeOut * 2.0f);
|
||||
|
||||
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
|
||||
client.FirstNewEventID = 0;
|
||||
client.NeedsMidRoundSync = true;
|
||||
client.MidRoundSyncTimeOut = Timing.TotalTime + midRoundSyncTimeOut;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the events from the message, ignoring ones we've already received
|
||||
/// </summary>
|
||||
public void Read(NetIncomingMessage msg, Client sender = null)
|
||||
{
|
||||
UInt16 firstEventID = msg.ReadUInt16();
|
||||
int eventCount = msg.ReadByte();
|
||||
|
||||
for (int i = 0; i < eventCount; i++)
|
||||
{
|
||||
UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
|
||||
UInt16 entityID = msg.ReadUInt16();
|
||||
|
||||
if (entityID == 0)
|
||||
{
|
||||
msg.ReadPadBits();
|
||||
sender.lastSentEntityEventID++;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte msgLength = msg.ReadByte();
|
||||
|
||||
IClientSerializable entity = Entity.FindEntityByID(entityID) as IClientSerializable;
|
||||
|
||||
//skip the event if we've already received it or if the entity isn't found
|
||||
if (thisEventID != (UInt16)(sender.lastSentEntityEventID + 1) || entity == null)
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
if (thisEventID != (UInt16) (sender.lastSentEntityEventID + 1))
|
||||
{
|
||||
DebugConsole.NewMessage("received msg " + thisEventID, Microsoft.Xna.Framework.Color.Red);
|
||||
}
|
||||
else if (entity == null)
|
||||
{
|
||||
DebugConsole.NewMessage(
|
||||
"received msg " + thisEventID + ", entity " + entityID + " not found",
|
||||
Microsoft.Xna.Framework.Color.Red);
|
||||
}
|
||||
}
|
||||
msg.Position += msgLength * 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
|
||||
}
|
||||
|
||||
UInt16 characterStateID = msg.ReadUInt16();
|
||||
|
||||
NetBuffer buffer = new NetBuffer();
|
||||
buffer.Write(msg.ReadBytes(msgLength - 2));
|
||||
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
|
||||
|
||||
sender.lastSentEntityEventID++;
|
||||
}
|
||||
msg.ReadPadBits();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
|
||||
{
|
||||
var serverEvent = entityEvent as ServerEntityEvent;
|
||||
if (serverEvent == null) return;
|
||||
|
||||
serverEvent.Write(buffer, recipient);
|
||||
}
|
||||
|
||||
protected void ReadEvent(NetBuffer buffer, INetSerializable entity, Client sender = null)
|
||||
{
|
||||
var clientEntity = entity as IClientSerializable;
|
||||
if (clientEntity == null) return;
|
||||
|
||||
clientEntity.ServerRead(ClientNetObject.ENTITY_STATE, buffer, sender);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ID = 0;
|
||||
events.Clear();
|
||||
|
||||
bufferedEvents.Clear();
|
||||
|
||||
lastSentToAll = 0;
|
||||
|
||||
uniqueEvents.Clear();
|
||||
|
||||
foreach (Client c in server.ConnectedClients)
|
||||
{
|
||||
c.entityEventLastSent.Clear();
|
||||
c.lastRecvEntityEventID = 0;
|
||||
c.lastSentEntityEventID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetIdUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Is newID more recent than oldID
|
||||
/// </summary>
|
||||
public static bool IdMoreRecent(ushort newID, ushort oldID)
|
||||
{
|
||||
uint id1 = newID;
|
||||
uint id2 = oldID;
|
||||
|
||||
return
|
||||
(id1 > id2) && (id1 - id2 <= ushort.MaxValue / 2)
|
||||
||
|
||||
(id2 > id1) && (id2 - id1 > ushort.MaxValue / 2);
|
||||
}
|
||||
|
||||
public static ushort Clamp(ushort id, ushort min, ushort max)
|
||||
{
|
||||
if (IdMoreRecent(min, max))
|
||||
{
|
||||
throw new ArgumentException("Min cannot be larger than max");
|
||||
}
|
||||
|
||||
if (!IdMoreRecent(id, min))
|
||||
{
|
||||
return min;
|
||||
}
|
||||
else if (IdMoreRecent(id, max))
|
||||
{
|
||||
return max;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public static void Test()
|
||||
{
|
||||
Debug.Assert(NetIdUtils.IdMoreRecent((ushort)2, (ushort)1));
|
||||
Debug.Assert(NetIdUtils.IdMoreRecent((ushort)2, (ushort)(ushort.MaxValue - 5)));
|
||||
Debug.Assert(!NetIdUtils.IdMoreRecent((ushort)ushort.MaxValue, (ushort)5));
|
||||
|
||||
Debug.Assert(Clamp((ushort)5, (ushort)1, (ushort)10) == 5);
|
||||
Debug.Assert(Clamp((ushort)(ushort.MaxValue - 5), (ushort)(ushort.MaxValue - 2), (ushort)3) == (ushort)(ushort.MaxValue - 2));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using Lidgren.Network;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum ClientPacketHeader
|
||||
{
|
||||
REQUEST_AUTH, //ask the server if a password is needed, if so we'll get nonce for encryption
|
||||
REQUEST_INIT, //ask the server to give you initialization
|
||||
UPDATE_LOBBY, //update state in lobby
|
||||
UPDATE_INGAME, //update state ingame
|
||||
|
||||
FILE_REQUEST, //request a (submarine) file from the server
|
||||
|
||||
RESPONSE_STARTGAME, //tell the server whether you're ready to start
|
||||
SERVER_COMMAND //tell the server to end a round or kick/ban someone (special permissions required)
|
||||
}
|
||||
enum ClientNetObject
|
||||
{
|
||||
END_OF_MESSAGE, //self-explanatory
|
||||
SYNC_IDS, //ids of the last changes the client knows about
|
||||
CHAT_MESSAGE, //also self-explanatory
|
||||
VOTE, //you get the idea
|
||||
CHARACTER_INPUT,
|
||||
ENTITY_STATE
|
||||
}
|
||||
|
||||
enum ServerPacketHeader
|
||||
{
|
||||
AUTH_RESPONSE, //tell the player if they require a password to log in
|
||||
AUTH_FAILURE, //the server won't authorize player yet, however connection is still alive
|
||||
UPDATE_LOBBY, //update state in lobby (votes and chat messages)
|
||||
UPDATE_INGAME, //update state ingame (character input and chat messages)
|
||||
|
||||
PERMISSIONS, //tell the client which special permissions they have (if any)
|
||||
|
||||
FILE_TRANSFER,
|
||||
|
||||
QUERY_STARTGAME, //ask the clients whether they're ready to start
|
||||
STARTGAME, //start a new round
|
||||
ENDGAME
|
||||
}
|
||||
enum ServerNetObject
|
||||
{
|
||||
END_OF_MESSAGE,
|
||||
SYNC_IDS,
|
||||
CHAT_MESSAGE,
|
||||
VOTE,
|
||||
ENTITY_POSITION,
|
||||
ENTITY_EVENT,
|
||||
ENTITY_EVENT_INITIAL
|
||||
}
|
||||
|
||||
enum VoteType
|
||||
{
|
||||
Unknown,
|
||||
Sub,
|
||||
Mode,
|
||||
EndRound,
|
||||
Kick
|
||||
}
|
||||
|
||||
abstract partial class NetworkMember
|
||||
{
|
||||
#if DEBUG
|
||||
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
|
||||
#endif
|
||||
|
||||
public NetPeer netPeer
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
protected string name;
|
||||
|
||||
protected TimeSpan updateInterval;
|
||||
protected DateTime updateTimer;
|
||||
|
||||
public int EndVoteCount, EndVoteMax;
|
||||
//private GUITextBlock endVoteText;
|
||||
|
||||
public int Port;
|
||||
|
||||
protected bool gameStarted;
|
||||
|
||||
public Dictionary<string, bool> monsterEnabled;
|
||||
|
||||
protected RespawnManager respawnManager;
|
||||
|
||||
public Voting Voting;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return;
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool GameStarted
|
||||
{
|
||||
get { return gameStarted; }
|
||||
}
|
||||
|
||||
public virtual List<Client> ConnectedClients
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public NetworkMember()
|
||||
{
|
||||
InitProjSpecific();
|
||||
|
||||
Voting = new Voting();
|
||||
}
|
||||
|
||||
public bool CanUseRadio(Character sender)
|
||||
{
|
||||
if (sender == null) return false;
|
||||
|
||||
var radio = sender.Inventory.Items.FirstOrDefault(i => i != null && i.GetComponent<WifiComponent>() != null);
|
||||
if (radio == null || !sender.HasEquippedItem(radio)) return false;
|
||||
|
||||
var radioComponent = radio.GetComponent<WifiComponent>();
|
||||
if (radioComponent == null) return false;
|
||||
return radioComponent.HasRequiredContainedItems(false);
|
||||
}
|
||||
|
||||
public void AddChatMessage(string message, ChatMessageType type, string senderName="", Character senderCharacter = null)
|
||||
{
|
||||
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter));
|
||||
}
|
||||
|
||||
public void AddChatMessage(ChatMessage message)
|
||||
{
|
||||
GameServer.Log(message.TextWithSender, ServerLog.MessageType.Chat);
|
||||
|
||||
string displayedText = message.Text;
|
||||
|
||||
if (message.Sender != null && !message.Sender.IsDead)
|
||||
{
|
||||
message.Sender.ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.Type]);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.NewChatMessage(message);
|
||||
|
||||
while (chatBox.CountChildren > 20)
|
||||
{
|
||||
chatBox.RemoveChild(chatBox.children[1]);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(message.SenderName))
|
||||
{
|
||||
displayedText = message.SenderName + ": " + displayedText;
|
||||
}
|
||||
|
||||
GUITextBlock msg = new GUITextBlock(new Rectangle(0, 0, chatBox.Rect.Width - 40, 0), displayedText,
|
||||
((chatBox.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f, message.Color,
|
||||
Alignment.Left, Alignment.TopLeft, "", null, true, GUI.SmallFont);
|
||||
msg.UserData = message.SenderName;
|
||||
|
||||
msg.Padding = new Vector4(20.0f, 0, 0, 0);
|
||||
|
||||
float prevSize = chatBox.BarSize;
|
||||
|
||||
msg.Padding = new Vector4(20, 0, 0, 0);
|
||||
chatBox.AddChild(msg);
|
||||
|
||||
if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f;
|
||||
|
||||
GUISoundType soundType = GUISoundType.Message;
|
||||
if (message.Type == ChatMessageType.Radio)
|
||||
{
|
||||
soundType = GUISoundType.RadioMessage;
|
||||
}
|
||||
else if (message.Type == ChatMessageType.Dead)
|
||||
{
|
||||
soundType = GUISoundType.DeadMessage;
|
||||
}
|
||||
|
||||
GUI.PlayUISound(soundType);
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void KickPlayer(string kickedName, bool ban, bool range = false) { }
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
#if CLIENT
|
||||
if (gameStarted && Screen.Selected == GameMain.GameScreen)
|
||||
{
|
||||
chatMsgBox.Visible = Character.Controlled == null || Character.Controlled.CanSpeak;
|
||||
|
||||
inGameHUD.Update(deltaTime);
|
||||
|
||||
GameMain.GameSession.CrewManager.Update(deltaTime);
|
||||
|
||||
if (Character.Controlled == null || Character.Controlled.IsDead)
|
||||
{
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayerInput.KeyHit(InputType.Chat) && chatMsgBox.Visible)
|
||||
{
|
||||
if (chatMsgBox.Selected)
|
||||
{
|
||||
chatMsgBox.Text = "";
|
||||
chatMsgBox.Deselect();
|
||||
}
|
||||
else
|
||||
{
|
||||
chatMsgBox.Select();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void Disconnect() { }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class RespawnManager : Entity, IServerSerializable
|
||||
{
|
||||
private readonly float respawnInterval;
|
||||
private float maxTransportTime;
|
||||
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
Transporting,
|
||||
Returning
|
||||
}
|
||||
|
||||
private NetworkMember networkMember;
|
||||
|
||||
private State state;
|
||||
|
||||
private Submarine respawnShuttle;
|
||||
private Steering shuttleSteering;
|
||||
private List<Door> shuttleDoors;
|
||||
|
||||
/// <summary>
|
||||
/// How long until the shuttle is dispatched with respawned characters
|
||||
/// </summary>
|
||||
public float RespawnTimer
|
||||
{
|
||||
get { return respawnTimer; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// how long until the shuttle starts heading back out of the level
|
||||
/// </summary>
|
||||
public float TransportTimer
|
||||
{
|
||||
get { return shuttleTransportTimer; }
|
||||
}
|
||||
|
||||
public bool CountdownStarted
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public State CurrentState
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
private float respawnTimer, shuttleReturnTimer, shuttleTransportTimer;
|
||||
|
||||
private float updateReturnTimer;
|
||||
|
||||
public RespawnManager(NetworkMember networkMember, Submarine shuttle)
|
||||
: base(shuttle)
|
||||
{
|
||||
this.networkMember = networkMember;
|
||||
|
||||
respawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true);
|
||||
respawnShuttle.Load(false);
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
//respawnShuttle.GodMode = true;
|
||||
|
||||
shuttleDoors = new List<Door>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != respawnShuttle) continue;
|
||||
|
||||
var steering = item.GetComponent<Steering>();
|
||||
if (steering != null) shuttleSteering = steering;
|
||||
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null) shuttleDoors.Add(door);
|
||||
|
||||
//lock all wires to prevent the players from messing up the electronics
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null)
|
||||
{
|
||||
foreach (Connection connection in connectionPanel.Connections)
|
||||
{
|
||||
Array.ForEach(connection.Wires, w => { if (w != null) w.Locked = true; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var server = networkMember as GameServer;
|
||||
if (server != null)
|
||||
{
|
||||
respawnInterval = server.RespawnInterval;
|
||||
maxTransportTime = server.MaxTransportTime;
|
||||
}
|
||||
|
||||
respawnTimer = respawnInterval;
|
||||
}
|
||||
|
||||
private List<Client> GetClientsToRespawn()
|
||||
{
|
||||
return networkMember.ConnectedClients.FindAll(c => c.inGame && (c.Character == null || c.Character.IsDead));
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case State.Waiting:
|
||||
UpdateWaiting(deltaTime);
|
||||
break;
|
||||
case State.Transporting:
|
||||
UpdateTransporting(deltaTime);
|
||||
break;
|
||||
case State.Returning:
|
||||
UpdateReturning(deltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWaiting(float deltaTime)
|
||||
{
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null)
|
||||
{
|
||||
if (CountdownStarted)
|
||||
{
|
||||
respawnTimer = Math.Max(0.0f, respawnTimer - deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
respawnShuttle.Velocity = Vector2.Zero;
|
||||
|
||||
shuttleSteering.AutoPilot = false;
|
||||
shuttleSteering.MaintainPos = false;
|
||||
|
||||
int characterToRespawnCount = GetClientsToRespawn().Count;
|
||||
int totalCharacterCount = server.ConnectedClients.Count;
|
||||
if (server.Character != null)
|
||||
{
|
||||
totalCharacterCount++;
|
||||
if (server.Character.IsDead) characterToRespawnCount++;
|
||||
}
|
||||
bool startCountdown = (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * server.MinRespawnRatio, 1.0f);
|
||||
|
||||
if (startCountdown)
|
||||
{
|
||||
if (!CountdownStarted)
|
||||
{
|
||||
CountdownStarted = true;
|
||||
server.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CountdownStarted = false;
|
||||
}
|
||||
|
||||
if (!CountdownStarted) return;
|
||||
|
||||
respawnTimer -= deltaTime;
|
||||
if (respawnTimer <= 0.0f)
|
||||
{
|
||||
respawnTimer = respawnInterval;
|
||||
|
||||
DispatchShuttle();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTransporting(float deltaTime)
|
||||
{
|
||||
//infinite transport time -> shuttle wont return
|
||||
if (maxTransportTime <= 0.0f) return;
|
||||
|
||||
shuttleTransportTimer -= deltaTime;
|
||||
|
||||
if (shuttleTransportTimer + deltaTime > 15.0f && shuttleTransportTimer <= 15.0f &&
|
||||
networkMember.Character != null &&
|
||||
networkMember.Character.Submarine == respawnShuttle)
|
||||
{
|
||||
networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server);
|
||||
}
|
||||
|
||||
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
//if there are no living chracters inside, transporting can be stopped immediately
|
||||
if (!Character.CharacterList.Any(c => c.Submarine == respawnShuttle && !c.IsDead))
|
||||
{
|
||||
shuttleTransportTimer = 0.0f;
|
||||
}
|
||||
|
||||
if (shuttleTransportTimer <= 0.0f)
|
||||
{
|
||||
GameServer.Log("The respawn shuttle is leaving.", ServerLog.MessageType.ServerMessage);
|
||||
state = State.Returning;
|
||||
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
CountdownStarted = false;
|
||||
shuttleReturnTimer = maxTransportTime;
|
||||
shuttleTransportTimer = maxTransportTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateReturning(float deltaTime)
|
||||
{
|
||||
//if (shuttleReturnTimer == maxTransportTime &&
|
||||
// networkMember.Character != null &&
|
||||
// networkMember.Character.Submarine == respawnShuttle)
|
||||
//{
|
||||
// networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server);
|
||||
//}
|
||||
|
||||
shuttleReturnTimer -= deltaTime;
|
||||
|
||||
updateReturnTimer += deltaTime;
|
||||
|
||||
if (updateReturnTimer > 1.0f)
|
||||
{
|
||||
updateReturnTimer = 0.0f;
|
||||
|
||||
respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.ShaftBody);
|
||||
|
||||
shuttleSteering.SetDestinationLevelStart();
|
||||
|
||||
foreach (Door door in shuttleDoors)
|
||||
{
|
||||
if (door.IsOpen) door.SetState(false,false,true);
|
||||
}
|
||||
|
||||
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null);
|
||||
shuttleGaps.ForEach(g => g.Remove());
|
||||
|
||||
var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == respawnShuttle && i.GetComponent<DockingPort>() != null);
|
||||
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
||||
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
//shuttle has returned if the path has been traversed or the shuttle is close enough to the exit
|
||||
|
||||
if (!CoroutineManager.IsCoroutineRunning("forcepos"))
|
||||
{
|
||||
if (shuttleSteering.SteeringPath != null && shuttleSteering.SteeringPath.Finished
|
||||
|| (respawnShuttle.WorldPosition.Y + respawnShuttle.Borders.Y > Level.Loaded.StartPosition.Y - Level.ShaftHeight &&
|
||||
Math.Abs(Level.Loaded.StartPosition.X - respawnShuttle.WorldPosition.X) < 1000.0f))
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
CoroutineManager.StartCoroutine(
|
||||
ForceShuttleToPos(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + 1000.0f), 100.0f), "forcepos");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (respawnShuttle.WorldPosition.Y > Level.Loaded.Size.Y || shuttleReturnTimer <= 0.0f)
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
state = State.Waiting;
|
||||
GameServer.Log("The respawn shuttle has left.", ServerLog.MessageType.ServerMessage);
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
respawnTimer = respawnInterval;
|
||||
CountdownStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchShuttle()
|
||||
{
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
state = State.Transporting;
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
shuttleSteering.TargetVelocity = Vector2.Zero;
|
||||
|
||||
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
RespawnCharacters();
|
||||
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
||||
}
|
||||
|
||||
private IEnumerable<object> ForceShuttleToPos(Vector2 position, float speed)
|
||||
{
|
||||
respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.ShaftBody);
|
||||
|
||||
while (Math.Abs(position.Y - respawnShuttle.WorldPosition.Y) > 100.0f)
|
||||
{
|
||||
Vector2 displayVel = Vector2.Normalize(position - respawnShuttle.WorldPosition) * speed;
|
||||
respawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
if (respawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.ShaftBody);
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private void ResetShuttle()
|
||||
{
|
||||
shuttleTransportTimer = maxTransportTime;
|
||||
shuttleReturnTimer = maxTransportTime;
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != respawnShuttle) continue;
|
||||
|
||||
if (item.body != null && item.body.Enabled && item.ParentInventory == null)
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
item.Condition = 100.0f;
|
||||
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
if (powerContainer != null)
|
||||
{
|
||||
powerContainer.Charge = powerContainer.Capacity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != respawnShuttle) continue;
|
||||
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -100000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null);
|
||||
shuttleGaps.ForEach(g => g.Remove());
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine != respawnShuttle) continue;
|
||||
|
||||
hull.OxygenPercentage = 100.0f;
|
||||
hull.Volume = 0.0f;
|
||||
}
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine == respawnShuttle)
|
||||
{
|
||||
if (Character.Controlled == c) Character.Controlled = null;
|
||||
c.Enabled = false;
|
||||
|
||||
if (c.Inventory != null)
|
||||
{
|
||||
foreach (Item item in c.Inventory.Items)
|
||||
{
|
||||
if (item == null) continue;
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
c.Kill(CauseOfDeath.Damage, true);
|
||||
}
|
||||
}
|
||||
|
||||
respawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + respawnShuttle.Borders.Height));
|
||||
|
||||
respawnShuttle.Velocity = Vector2.Zero;
|
||||
|
||||
respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.ShaftBody);
|
||||
|
||||
}
|
||||
|
||||
public void RespawnCharacters()
|
||||
{
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
|
||||
var clients = GetClientsToRespawn();
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
//all characters are in Team 1 in game modes/missions with only one team.
|
||||
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
|
||||
c.TeamID = 1;
|
||||
if (c.characterInfo == null) c.characterInfo = new CharacterInfo(Character.HumanConfigFile, c.name);
|
||||
}
|
||||
|
||||
List<CharacterInfo> characterInfos = clients.Select(c => c.characterInfo).ToList();
|
||||
if (server.Character != null && server.Character.IsDead) characterInfos.Add(server.CharacterInfo);
|
||||
|
||||
GameServer.Log("Respawning characters: "+string.Join(", ", characterInfos.Select(ci => ci.Name)), ServerLog.MessageType.ServerMessage);
|
||||
|
||||
server.AssignJobs(clients, server.Character != null && server.Character.IsDead);
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
c.characterInfo.Job = new Job(c.assignedJob);
|
||||
}
|
||||
|
||||
//the spawnpoints where the characters will spawn
|
||||
var shuttleSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, respawnShuttle);
|
||||
//the spawnpoints where they would spawn if they were spawned inside the main sub
|
||||
//(in order to give them appropriate ID card tags)
|
||||
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
|
||||
|
||||
ItemPrefab divingSuitPrefab = ItemPrefab.list.Find(ip => ip.Name == "Diving Suit") as ItemPrefab;
|
||||
ItemPrefab oxyPrefab = ItemPrefab.list.Find(ip => ip.Name == "Oxygen Tank") as ItemPrefab;
|
||||
ItemPrefab scooterPrefab = ItemPrefab.list.Find(ip => ip.Name == "Underwater Scooter") as ItemPrefab;
|
||||
ItemPrefab batteryPrefab = ItemPrefab.list.Find(ip => ip.Name == "Battery Cell") as ItemPrefab;
|
||||
|
||||
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnShuttle && wp.SpawnType == SpawnType.Cargo);
|
||||
|
||||
for (int i = 0; i < characterInfos.Count; i++)
|
||||
{
|
||||
bool myCharacter = false;
|
||||
#if CLIENT
|
||||
myCharacter = i >= clients.Count;
|
||||
#endif
|
||||
|
||||
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, !myCharacter, false);
|
||||
|
||||
character.TeamID = 1;
|
||||
|
||||
#if CLIENT
|
||||
if (myCharacter)
|
||||
{
|
||||
server.Character = character;
|
||||
Character.Controlled = character;
|
||||
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
clients[i].Character = character;
|
||||
#if CLIENT
|
||||
}
|
||||
#endif
|
||||
|
||||
Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position;
|
||||
|
||||
if (divingSuitPrefab != null && oxyPrefab != null)
|
||||
{
|
||||
var divingSuit = new Item(divingSuitPrefab, pos, respawnShuttle);
|
||||
Entity.Spawner.CreateNetworkEvent(divingSuit, false);
|
||||
|
||||
var oxyTank = new Item(oxyPrefab, pos, respawnShuttle);
|
||||
Entity.Spawner.CreateNetworkEvent(oxyTank, false);
|
||||
divingSuit.Combine(oxyTank);
|
||||
}
|
||||
|
||||
if (scooterPrefab != null && batteryPrefab != null)
|
||||
{
|
||||
var scooter = new Item(scooterPrefab, pos, respawnShuttle);
|
||||
Entity.Spawner.CreateNetworkEvent(scooter, false);
|
||||
|
||||
var battery = new Item(batteryPrefab, pos, respawnShuttle);
|
||||
Entity.Spawner.CreateNetworkEvent(battery, false);
|
||||
|
||||
scooter.Combine(battery);
|
||||
}
|
||||
|
||||
//give the character the items they would've gotten if they had spawned in the main sub
|
||||
character.GiveJobItems(mainSubSpawnPoints[i]);
|
||||
|
||||
//add the ID card tags they should've gotten when spawning in the shuttle
|
||||
foreach (Item item in character.Inventory.Items)
|
||||
{
|
||||
if (item == null || item.Prefab.Name != "ID Card") continue;
|
||||
foreach (string s in shuttleSpawnPoints[i].IdCardTags)
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.characters.Add(character);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)state);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case State.Transporting:
|
||||
msg.Write(TransportTimer);
|
||||
break;
|
||||
case State.Waiting:
|
||||
msg.Write(CountdownStarted);
|
||||
msg.Write(respawnTimer);
|
||||
break;
|
||||
case State.Returning:
|
||||
break;
|
||||
}
|
||||
|
||||
msg.WritePadBits();
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
||||
{
|
||||
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
|
||||
|
||||
switch (newState)
|
||||
{
|
||||
case State.Transporting:
|
||||
maxTransportTime = msg.ReadSingle();
|
||||
shuttleTransportTimer = maxTransportTime;
|
||||
CountdownStarted = false;
|
||||
|
||||
if (state != newState)
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
||||
}
|
||||
break;
|
||||
case State.Waiting:
|
||||
CountdownStarted = msg.ReadBoolean();
|
||||
ResetShuttle();
|
||||
respawnTimer = msg.ReadSingle();
|
||||
break;
|
||||
case State.Returning:
|
||||
CountdownStarted = false;
|
||||
break;
|
||||
}
|
||||
state = newState;
|
||||
|
||||
msg.ReadPadBits();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class ServerLog
|
||||
{
|
||||
private struct LogMessage
|
||||
{
|
||||
public readonly string Text;
|
||||
public readonly MessageType Type;
|
||||
|
||||
public LogMessage(string text, MessageType type)
|
||||
{
|
||||
Text = "[" + DateTime.Now.ToString() + "] " + text;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageType
|
||||
{
|
||||
Chat,
|
||||
ItemInteraction,
|
||||
Inventory,
|
||||
Attack,
|
||||
ServerMessage,
|
||||
Error
|
||||
}
|
||||
|
||||
private readonly Color[] messageColor =
|
||||
{
|
||||
Color.LightBlue,
|
||||
new Color(255, 142, 0),
|
||||
new Color(238, 208, 0),
|
||||
new Color(204, 74, 78),
|
||||
new Color(157, 225, 160),
|
||||
Color.Red
|
||||
};
|
||||
|
||||
private readonly string[] messageTypeName =
|
||||
{
|
||||
"Chat message",
|
||||
"Item interaction",
|
||||
"Inventory usage",
|
||||
"Attack & death",
|
||||
"Server message",
|
||||
"Error"
|
||||
};
|
||||
|
||||
private int linesPerFile = 800;
|
||||
|
||||
public const string SavePath = "ServerLogs";
|
||||
|
||||
private string serverName;
|
||||
|
||||
private readonly Queue<LogMessage> lines;
|
||||
|
||||
private int unsavedLineCount;
|
||||
|
||||
private string msgFilter;
|
||||
private bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
|
||||
|
||||
public int LinesPerFile
|
||||
{
|
||||
get { return linesPerFile; }
|
||||
set { linesPerFile = Math.Max(value, 10); }
|
||||
}
|
||||
|
||||
public ServerLog(string serverName)
|
||||
{
|
||||
this.serverName = serverName;
|
||||
|
||||
lines = new Queue<LogMessage>();
|
||||
}
|
||||
|
||||
public void WriteLine(string line, MessageType messageType)
|
||||
{
|
||||
//string logLine = "[" + DateTime.Now.ToLongTimeString() + "] " + line;
|
||||
|
||||
#if SERVER
|
||||
DebugConsole.NewMessage(line, Color.White); //TODO: REMOVE
|
||||
#endif
|
||||
|
||||
var newText = new LogMessage(line, messageType);
|
||||
|
||||
lines.Enqueue(newText);
|
||||
|
||||
#if CLIENT
|
||||
if (LogFrame != null)
|
||||
{
|
||||
AddLine(newText);
|
||||
|
||||
listBox.UpdateScrollBarSize();
|
||||
}
|
||||
#endif
|
||||
|
||||
unsavedLineCount++;
|
||||
|
||||
if (unsavedLineCount >= LinesPerFile)
|
||||
{
|
||||
Save();
|
||||
unsavedLineCount = 0;
|
||||
}
|
||||
|
||||
while (lines.Count > LinesPerFile)
|
||||
{
|
||||
lines.Dequeue();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
while (listBox != null && listBox.children.Count > LinesPerFile)
|
||||
{
|
||||
listBox.RemoveChild(listBox.children[0]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (!Directory.Exists(SavePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(SavePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to create a folder for server logs", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string fileName = serverName+"_"+DateTime.Now.ToShortDateString()+"_"+DateTime.Now.ToShortTimeString()+".txt";
|
||||
|
||||
fileName = fileName.Replace(":", "");
|
||||
fileName = fileName.Replace("../", "");
|
||||
fileName = fileName.Replace("/", "");
|
||||
|
||||
string filePath = Path.Combine(SavePath, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(filePath, lines.Select(l => l.Text));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving the server log to " + filePath + " failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Voting
|
||||
{
|
||||
private bool allowSubVoting, allowModeVoting;
|
||||
|
||||
public bool AllowVoteKick = true;
|
||||
|
||||
public bool AllowEndVoting = true;
|
||||
|
||||
private List<Pair<object, int>> GetVoteList(VoteType voteType, List<Client> voters)
|
||||
{
|
||||
List<Pair<object, int>> voteList = new List<Pair<object, int>>();
|
||||
|
||||
foreach (Client voter in voters)
|
||||
{
|
||||
object vote = voter.GetVote<object>(voteType);
|
||||
if (vote == null) continue;
|
||||
|
||||
var existingVotable = voteList.Find(v => v.First == vote || v.First.Equals(vote));
|
||||
if (existingVotable == null)
|
||||
{
|
||||
voteList.Add(Pair<object, int>.Create(vote, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
existingVotable.Second++;
|
||||
}
|
||||
}
|
||||
return voteList;
|
||||
}
|
||||
|
||||
public T HighestVoted<T>(VoteType voteType, List<Client> voters)
|
||||
{
|
||||
if (voteType == VoteType.Sub && !AllowSubVoting) return default(T);
|
||||
if (voteType == VoteType.Mode && !AllowModeVoting) return default(T);
|
||||
|
||||
List<Pair<object, int>> voteList = GetVoteList(voteType,voters);
|
||||
|
||||
T selected = default(T);
|
||||
int highestVotes = 0;
|
||||
foreach (Pair<object, int> votable in voteList)
|
||||
{
|
||||
if (selected == null || votable.Second > highestVotes)
|
||||
{
|
||||
highestVotes = votable.Second;
|
||||
selected = (T)votable.First;
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void ResetVotes(List<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes();
|
||||
}
|
||||
|
||||
GameMain.NetworkMember.EndVoteCount = 0;
|
||||
GameMain.NetworkMember.EndVoteMax = 0;
|
||||
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(connectedClients, VoteType.Mode);
|
||||
UpdateVoteTexts(connectedClients, VoteType.Sub);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ServerRead(NetIncomingMessage inc, Client sender)
|
||||
{
|
||||
if (GameMain.Server == null || sender == null) return;
|
||||
|
||||
byte voteTypeByte = inc.ReadByte();
|
||||
VoteType voteType = VoteType.Unknown;
|
||||
try
|
||||
{
|
||||
voteType = (VoteType)voteTypeByte;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.Sub:
|
||||
string subName = inc.ReadString();
|
||||
Submarine sub = Submarine.SavedSubmarines.Find(s => s.Name == subName);
|
||||
sender.SetVote(voteType, sub);
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(GameMain.Server.ConnectedClients, voteType);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case VoteType.Mode:
|
||||
string modeName = inc.ReadString();
|
||||
GameModePreset mode = GameModePreset.list.Find(gm => gm.Name == modeName);
|
||||
sender.SetVote(voteType, mode);
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(GameMain.Server.ConnectedClients, voteType);
|
||||
#endif
|
||||
break;
|
||||
case VoteType.EndRound:
|
||||
if (sender.Character == null) return;
|
||||
sender.SetVote(voteType, inc.ReadBoolean());
|
||||
|
||||
GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.Character != null && c.GetVote<bool>(VoteType.EndRound));
|
||||
GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.Character != null);
|
||||
|
||||
break;
|
||||
case VoteType.Kick:
|
||||
byte kickedClientID = inc.ReadByte();
|
||||
|
||||
Client kicked = GameMain.Server.ConnectedClients.Find(c => c.ID == kickedClientID);
|
||||
if (kicked != null)
|
||||
{
|
||||
kicked.AddKickVote(sender);
|
||||
Client.UpdateKickVotes(GameMain.Server.ConnectedClients);
|
||||
|
||||
GameMain.Server.SendChatMessage(sender.name + " has voted to kick " + kicked.name, ChatMessageType.Server, null);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
inc.ReadPadBits();
|
||||
|
||||
GameMain.Server.UpdateVoteStatus();
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
msg.Write(allowSubVoting);
|
||||
if (allowSubVoting)
|
||||
{
|
||||
List<Pair<object, int>> voteList = GetVoteList(VoteType.Sub, GameMain.Server.ConnectedClients);
|
||||
msg.Write((byte)voteList.Count);
|
||||
foreach (Pair<object, int> vote in voteList)
|
||||
{
|
||||
msg.Write((byte)vote.Second);
|
||||
msg.Write(((Submarine)vote.First).Name);
|
||||
}
|
||||
}
|
||||
msg.Write(AllowModeVoting);
|
||||
if (allowModeVoting)
|
||||
{
|
||||
List<Pair<object, int>> voteList = GetVoteList(VoteType.Mode, GameMain.Server.ConnectedClients);
|
||||
msg.Write((byte)voteList.Count);
|
||||
foreach (Pair<object, int> vote in voteList)
|
||||
{
|
||||
msg.Write((byte)vote.Second);
|
||||
msg.Write(((GameModePreset)vote.First).Name);
|
||||
}
|
||||
}
|
||||
msg.Write(AllowEndVoting);
|
||||
if (AllowEndVoting)
|
||||
{
|
||||
msg.Write((byte)GameMain.Server.ConnectedClients.Count(v => v.GetVote<bool>(VoteType.EndRound)));
|
||||
msg.Write((byte)GameMain.Server.ConnectedClients.Count);
|
||||
}
|
||||
|
||||
msg.Write(AllowVoteKick);
|
||||
|
||||
msg.WritePadBits();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class WhiteListedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
|
||||
public WhiteListedPlayer(string name,string ip)
|
||||
{
|
||||
Name = name;
|
||||
IP = ip;
|
||||
}
|
||||
}
|
||||
|
||||
partial class WhiteList
|
||||
{
|
||||
const string SavePath = "Data/whitelist.txt";
|
||||
|
||||
private List<WhiteListedPlayer> whitelistedPlayers;
|
||||
public List<WhiteListedPlayer> WhiteListedPlayers
|
||||
{
|
||||
get { return whitelistedPlayers; }
|
||||
}
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
public WhiteList()
|
||||
{
|
||||
Enabled = false;
|
||||
whitelistedPlayers = new List<WhiteListedPlayer>();
|
||||
|
||||
if (File.Exists(SavePath))
|
||||
{
|
||||
string[] lines;
|
||||
try
|
||||
{
|
||||
lines = File.ReadAllLines(SavePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to open whitelist in " + SavePath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line[0] == '#')
|
||||
{
|
||||
string lineval = line.Substring(1, line.Length - 1);
|
||||
int intVal = 0;
|
||||
Int32.TryParse(lineval, out intVal);
|
||||
if (lineval.ToLower() == "true" || intVal != 0)
|
||||
{
|
||||
Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Enabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] separatedLine = line.Split(',');
|
||||
if (separatedLine.Length < 2) continue;
|
||||
|
||||
string name = String.Join(",", separatedLine.Take(separatedLine.Length - 1));
|
||||
string ip = separatedLine.Last();
|
||||
|
||||
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
GameServer.Log("Saving whitelist", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
|
||||
if (Enabled)
|
||||
{
|
||||
lines.Add("#true");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add("#false");
|
||||
}
|
||||
foreach (WhiteListedPlayer wlp in whitelistedPlayers)
|
||||
{
|
||||
lines.Add(wlp.Name + "," + wlp.IP);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(SavePath, lines);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving the whitelist to " + SavePath + " failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWhiteListed(string name, string ip)
|
||||
{
|
||||
if (!Enabled) return true;
|
||||
WhiteListedPlayer wlp = whitelistedPlayers.Find(p => p.Name == name);
|
||||
if (wlp == null) return false;
|
||||
if (wlp.IP != ip && !string.IsNullOrWhiteSpace(wlp.IP)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RemoveFromWhiteList(WhiteListedPlayer wlp)
|
||||
{
|
||||
DebugConsole.Log("Removing " + wlp.Name + " from whitelist");
|
||||
GameServer.Log("Removing " + wlp.Name + " from whitelist", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
whitelistedPlayers.Remove(wlp);
|
||||
Save();
|
||||
}
|
||||
|
||||
private void AddToWhiteList(string name,string ip)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return;
|
||||
if (whitelistedPlayers.Any(x => x.Name.ToLower() == name.ToLower() && x.IP == ip)) return;
|
||||
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user