433 lines
19 KiB
C#
433 lines
19 KiB
C#
using Lidgren.Network;
|
|
using SRMultiplayer.Packets;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEngine;
|
|
|
|
namespace SRMultiplayer.Networking
|
|
{
|
|
public class NetworkServer : MonoBehaviour
|
|
{
|
|
public static NetworkServer Instance { get; private set; }
|
|
public NetServer m_Server;
|
|
public NetServer m_DiscoverServer;
|
|
|
|
public int Port;
|
|
|
|
public enum ServerStatus
|
|
{
|
|
Stopped,
|
|
Running
|
|
}
|
|
public ServerStatus Status;
|
|
public NetPeerStatistics Statistics { get { return m_Server.Statistics; } }
|
|
|
|
private void Awake()
|
|
{
|
|
Instance = this;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
Disconnect();
|
|
}
|
|
|
|
public void StartServer(int port)
|
|
{
|
|
Port = port;
|
|
Status = ServerStatus.Running;
|
|
|
|
NetPeerConfiguration config = new NetPeerConfiguration("srmp");
|
|
config.Port = port;
|
|
config.MaximumConnections = 250;
|
|
config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
|
|
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
|
|
|
|
m_Server = new NetServer(config);
|
|
m_Server.Start();
|
|
|
|
NetPeerConfiguration discoverConfig = new NetPeerConfiguration("discover");
|
|
discoverConfig.EnableMessageType(NetIncomingMessageType.DiscoveryRequest);
|
|
discoverConfig.MaximumConnections = 0;
|
|
discoverConfig.Port = 6996;
|
|
|
|
m_DiscoverServer = new NetServer(discoverConfig);
|
|
m_DiscoverServer.Start();
|
|
|
|
Globals.DisableAchievements = true;
|
|
Globals.PartyID = Guid.NewGuid();
|
|
|
|
byte id = 1;
|
|
while (id < 255 && Globals.Players.Values.Any(p => p.ID == id))
|
|
id++;
|
|
|
|
Globals.LocalID = id;
|
|
Globals.LocalPlayer = SRSingleton<SceneContext>.Instance.Player.AddComponent<NetworkPlayer>();
|
|
Globals.LocalPlayer.ID = id;
|
|
Globals.LocalPlayer.Username = Globals.Username;
|
|
Globals.LocalPlayer.HasLoaded = true;
|
|
Globals.LocalPlayer.Spawn();
|
|
Globals.Players.Add(id, Globals.LocalPlayer);
|
|
Globals.ClientLoaded = true;
|
|
|
|
Directory.CreateDirectory(Path.Combine(SRMP.ModDataPath, SRSingleton<GameContext>.Instance.AutoSaveDirector.SavedGame.GetName()));
|
|
|
|
foreach(var netRegion in Globals.Regions.Values)
|
|
{
|
|
if(netRegion.Region.root.activeInHierarchy)
|
|
{
|
|
netRegion.AddPlayer(Globals.LocalPlayer);
|
|
netRegion.TakeOwnership();
|
|
}
|
|
}
|
|
foreach(var actor in SRSingleton<SceneContext>.Instance.GameModel.AllActors().Values)
|
|
{
|
|
if (actor.ident != Identifiable.Id.NONE && actor.ident != Identifiable.Id.PLAYER && !Identifiable.SCENE_OBJECTS.Contains(actor.ident))
|
|
{
|
|
var netActor = actor.transform.gameObject.AddComponent<NetworkActor>();
|
|
netActor.ID = Utils.GetRandomActorID();
|
|
netActor.Ident = (ushort)actor.ident;
|
|
netActor.RegionSet = (byte)actor.currRegionSetId;
|
|
if (actor.transform.gameObject.activeInHierarchy)
|
|
{
|
|
netActor.Owner = Globals.LocalID;
|
|
}
|
|
|
|
Globals.Actors.Add(netActor.ID, netActor);
|
|
}
|
|
}
|
|
foreach(var landPlot in SRSingleton<SceneContext>.Instance.GameModel.AllLandPlots().Values)
|
|
{
|
|
var netLandPlot = landPlot.gameObj.GetComponent<NetworkLandplot>();
|
|
netLandPlot.Plot = landPlot.gameObj.GetComponentInChildren<LandPlot>(true);
|
|
}
|
|
foreach (TutorialDirector.Id tut in (TutorialDirector.Id[])Enum.GetValues(typeof(TutorialDirector.Id)))
|
|
{
|
|
SRSingleton<SceneContext>.Instance.TutorialDirector.tutModel.completedIds.Add(tut);
|
|
}
|
|
|
|
NetworkMasterServer.Instance.CreateServer(port);
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
Status = ServerStatus.Stopped;
|
|
m_Server?.Shutdown("goodbye");
|
|
m_DiscoverServer?.Shutdown("goodbye");
|
|
|
|
SRSingleton<NetworkMasterServer>.Instance.DeleteServer();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
NetIncomingMessage dim;
|
|
while ((dim = m_DiscoverServer?.ReadMessage()) != null)
|
|
{
|
|
switch (dim.MessageType)
|
|
{
|
|
case NetIncomingMessageType.DiscoveryRequest:
|
|
{
|
|
NetOutgoingMessage response = m_DiscoverServer.CreateMessage();
|
|
response.Write("Game of " + Globals.Username);
|
|
response.Write(Port);
|
|
|
|
// Send the response to the sender of the request
|
|
m_DiscoverServer.SendDiscoveryResponse(response, dim.SenderEndPoint);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
NetIncomingMessage im;
|
|
while ((im = m_Server?.ReadMessage()) != null)
|
|
{
|
|
switch (im.MessageType)
|
|
{
|
|
case NetIncomingMessageType.DebugMessage:
|
|
case NetIncomingMessageType.ErrorMessage:
|
|
case NetIncomingMessageType.WarningMessage:
|
|
case NetIncomingMessageType.VerboseDebugMessage:
|
|
{
|
|
string text = im.ReadString();
|
|
//SRMP.Log(text);
|
|
}
|
|
break;
|
|
case NetIncomingMessageType.NatIntroductionSuccess:
|
|
{
|
|
string token = im.ReadString();
|
|
SRMP.Log("[NetworkServer] Nat introduction success to " + im.SenderEndPoint + " token is: " + token);
|
|
}
|
|
break;
|
|
case NetIncomingMessageType.ConnectionApproval:
|
|
{
|
|
int version = im.ReadInt32();
|
|
Guid uuid = new Guid(im.ReadBytes(16));
|
|
string username = im.ReadString();
|
|
|
|
SRMP.Log($"{username} ({uuid} {im.SenderConnection.RemoteEndPoint.Address.ToString()}) connects with version {version}");
|
|
|
|
if (version != Globals.Version)
|
|
{
|
|
im.SenderConnection.Deny($"Version mismatch. Required version: {Globals.Version}");
|
|
break;
|
|
}
|
|
Regex rg = new Regex(@"^[a-zA-Z_][\w]*$");
|
|
if (!rg.IsMatch(username))
|
|
{
|
|
im.SenderConnection.Deny("Username invalid. Only A-Z, 0-9 and _ is allowed");
|
|
break;
|
|
}
|
|
if (!im.SenderConnection.RemoteEndPoint.Address.ToString().Equals("127.0.0.1") && Globals.Players.Values.Any(p => p.UUID == uuid))
|
|
{
|
|
im.SenderConnection.Deny("Someone with that UUID already online");
|
|
break;
|
|
}
|
|
|
|
List<string> mods = new List<string>();
|
|
int modsCount = im.ReadInt32();
|
|
for (int i = 0; i < modsCount; i++)
|
|
{
|
|
mods.Add(im.ReadString());
|
|
}
|
|
bool successMods = true;
|
|
int successModsCount = 0;
|
|
foreach (var mod in mods)
|
|
{
|
|
if (!Globals.Mods.Contains(mod) && !Globals.UserData.IgnoredMods.Contains(mod))
|
|
{
|
|
successMods = false;
|
|
break;
|
|
}
|
|
else if(!Globals.UserData.IgnoredMods.Contains(mod))
|
|
{
|
|
successModsCount++;
|
|
}
|
|
}
|
|
if (!successMods || successModsCount != Globals.Mods.Count)
|
|
{
|
|
var missingMods = new List<string>();
|
|
foreach (var m in Globals.Mods)
|
|
{
|
|
if (!mods.Contains(m))
|
|
{
|
|
missingMods.Add(m);
|
|
}
|
|
}
|
|
var clientMods = new List<string>();
|
|
foreach (var m in mods)
|
|
{
|
|
if (!Globals.Mods.Contains(m))
|
|
{
|
|
clientMods.Add(m);
|
|
}
|
|
}
|
|
string msg = "Mods mismatch.\n";
|
|
if (missingMods.Count > 0) msg += "You are missing following mods: " + String.Join(", ", missingMods.ToArray()) + "\n";
|
|
if (clientMods.Count > 0) msg += "The Server is missing following mods: " + String.Join(", ", clientMods.ToArray()) + "\n";
|
|
im.SenderConnection.Deny(msg);
|
|
break;
|
|
}
|
|
|
|
List<DLCPackage.Id> dlcs = new List<DLCPackage.Id>();
|
|
int dlcCount = im.ReadInt32();
|
|
for(int i = 0; i < dlcCount; i++)
|
|
{
|
|
dlcs.Add((DLCPackage.Id)im.ReadByte());
|
|
}
|
|
if (Globals.UserData.CheckDLC)
|
|
{
|
|
if (SRSingleton<GameContext>.Instance.DLCDirector.Installed.Count() != dlcs.Count)
|
|
{
|
|
im.SenderConnection.Deny("DLC mismatch. You need following DLCs: " + String.Join(", ", SRSingleton<GameContext>.Instance.DLCDirector.Installed) + "\nYou got: " + String.Join(", ", dlcs.ToArray()));
|
|
break;
|
|
}
|
|
bool successDLCs = true;
|
|
int successDLCsCount = 0;
|
|
foreach (var dlc in dlcs)
|
|
{
|
|
if (!SRSingleton<GameContext>.Instance.DLCDirector.Installed.Contains(dlc))
|
|
{
|
|
successDLCs = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
successDLCsCount++;
|
|
}
|
|
}
|
|
if (!successDLCs || successDLCsCount != SRSingleton<GameContext>.Instance.DLCDirector.Installed.Count())
|
|
{
|
|
im.SenderConnection.Deny("DLC mismatch. You need following DLCs: " + String.Join(", ", SRSingleton<GameContext>.Instance.DLCDirector.Installed) + "\nYou got: " + String.Join(", ", dlcs.ToArray()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
byte id = 1;
|
|
while (id < 255 && Globals.Players.Values.Any(p => p.ID == id))
|
|
id++;
|
|
|
|
var playerObj = new GameObject(username + "(" + id + ")");
|
|
var player = playerObj.AddComponent<NetworkPlayer>();
|
|
|
|
player.Connection = im.SenderConnection;
|
|
player.UUID = uuid;
|
|
player.ID = id;
|
|
player.Username = username;
|
|
player.Mods = mods;
|
|
player.DLCs = dlcs;
|
|
|
|
Globals.Players.Add(player.ID, player);
|
|
NetOutgoingMessage hail = CreateMessage();
|
|
hail.Write(id);
|
|
hail.Write(Globals.Players.Count);
|
|
foreach (var p in Globals.Players.Values.ToList())
|
|
{
|
|
hail.Write(p.ID);
|
|
hail.Write(p.Username);
|
|
hail.Write(p.HasLoaded);
|
|
}
|
|
hail.Write(Globals.PartyID.ToByteArray());
|
|
hail.Write((byte)SRSingleton<SceneContext>.Instance.GameModel.currGameMode);
|
|
hail.Write(SRSingleton<GameContext>.Instance.AutoSaveDirector.SavedGame.GetName());
|
|
im.SenderConnection.Approve(hail);
|
|
}
|
|
break;
|
|
case NetIncomingMessageType.StatusChanged:
|
|
{
|
|
NetConnectionStatus status = (NetConnectionStatus)im.ReadByte();
|
|
string reason = im.ReadString();
|
|
//SRMP.Log(NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);
|
|
|
|
var player = Globals.Players.Values.FirstOrDefault(p => p.Connection != null && p.Connection.RemoteUniqueIdentifier == im.SenderConnection.RemoteUniqueIdentifier);
|
|
if (player != null)
|
|
{
|
|
if (status == NetConnectionStatus.Connected)
|
|
{
|
|
SRMP.Log($"Player connected: {player} with following mods: {string.Join(", ", player.Mods)} and these DLCs: {String.Join(", ", player.DLCs)}");
|
|
new PacketPlayerJoined()
|
|
{
|
|
ID = player.ID,
|
|
Username = player.Username
|
|
}.SendToAllExcept(player);
|
|
}
|
|
else if (status == NetConnectionStatus.Disconnected)
|
|
{
|
|
if (player.HasLoaded)
|
|
{
|
|
player.Save();
|
|
}
|
|
if (player.gameObject != null)
|
|
{
|
|
Destroy(player.gameObject);
|
|
}
|
|
Globals.Players.Remove(player.ID);
|
|
|
|
player.OnLeft();
|
|
SRMP.Log($"Player disconnected: {player}");
|
|
SendToAll(new PacketPlayerLeft()
|
|
{
|
|
ID = player.ID
|
|
});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case NetIncomingMessageType.Data:
|
|
{
|
|
Globals.HandlePacket = true;
|
|
PacketType type = (PacketType)im.ReadUInt16();
|
|
|
|
var player = Globals.Players.Values.FirstOrDefault(p => p.Connection != null && p.Connection.RemoteUniqueIdentifier == im.SenderConnection.RemoteUniqueIdentifier);
|
|
if (player != null)
|
|
{
|
|
try
|
|
{
|
|
NetworkHandlerServer.HandlePacket(type, im, player);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
SRMP.Log($"[SERVER] Could not handle packet {type}\n{ex}");
|
|
}
|
|
}
|
|
Globals.HandlePacket = false;
|
|
}
|
|
break;
|
|
default:
|
|
SRMP.Log("Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes " + im.DeliveryMethod + "|" + im.SequenceChannel);
|
|
break;
|
|
}
|
|
m_Server.Recycle(im);
|
|
}
|
|
}
|
|
|
|
public void SendToAll(Packet packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
|
{
|
|
var om = CreateMessage();
|
|
packet.Serialize(om);
|
|
|
|
m_Server?.SendToAll(om, method, sequence);
|
|
}
|
|
|
|
public void SendToAll(NetOutgoingMessage packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
|
{
|
|
m_Server?.SendToAll(packet, method, sequence);
|
|
}
|
|
|
|
public void SendToAll(NetOutgoingMessage packet, NetworkPlayer player, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
|
{
|
|
m_Server?.SendToAll(packet, player.Connection, method, sequence);
|
|
}
|
|
|
|
internal void SendTo(Packet packet, List<NetConnection> cons, NetDeliveryMethod method, int sequence)
|
|
{
|
|
if (cons.Count > 0)
|
|
{
|
|
var om = CreateMessage();
|
|
packet.Serialize(om);
|
|
|
|
m_Server?.SendMessage(om, cons, method, sequence);
|
|
}
|
|
}
|
|
|
|
public void SendToAllExcept(Packet packet, NetworkPlayer player, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
|
{
|
|
var om = CreateMessage();
|
|
packet.Serialize(om);
|
|
|
|
List<NetConnection> cons = new List<NetConnection>();
|
|
foreach (var p in Globals.Players.Values.ToList())
|
|
{
|
|
if (p.ID != player.ID)
|
|
{
|
|
cons.Add(p.Connection);
|
|
}
|
|
}
|
|
if (cons.Count > 0)
|
|
{
|
|
m_Server?.SendMessage(om, cons, method, sequence);
|
|
}
|
|
}
|
|
|
|
public void Send(NetConnection connection, IPacket packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
|
{
|
|
var om = CreateMessage();
|
|
packet.Serialize(om);
|
|
|
|
connection.SendMessage(om, method, sequence);
|
|
}
|
|
|
|
public void SendUnconnected(NetOutgoingMessage om, IPEndPoint endPoint)
|
|
{
|
|
m_Server?.SendUnconnectedMessage(om, endPoint);
|
|
}
|
|
|
|
public NetOutgoingMessage CreateMessage()
|
|
{
|
|
return m_Server?.CreateMessage();
|
|
}
|
|
}
|
|
}
|