364 lines
14 KiB
C#
364 lines
14 KiB
C#
using System;
|
|
using TrueCraft.API.Server;
|
|
using TrueCraft.API.Networking;
|
|
using TrueCraft.Core.Networking;
|
|
using System.Threading;
|
|
using System.Net.Sockets;
|
|
using System.Net;
|
|
using System.Collections.Generic;
|
|
using TrueCraft.API.World;
|
|
using TrueCraft.API.Logging;
|
|
using TrueCraft.Core.Networking.Packets;
|
|
using TrueCraft.API;
|
|
using TrueCraft.Core.Logging;
|
|
using TrueCraft.API.Logic;
|
|
using TrueCraft.Exceptions;
|
|
|
|
namespace TrueCraft
|
|
{
|
|
public class MultiplayerServer : IMultiplayerServer
|
|
{
|
|
public event EventHandler<ChatMessageEventArgs> ChatMessageReceived;
|
|
public event EventHandler<PlayerJoinedEventArgs> PlayerJoined;
|
|
|
|
public IPacketReader PacketReader { get; private set; }
|
|
public IList<IRemoteClient> Clients { get; private set; }
|
|
public IList<IWorld> Worlds { get; private set; }
|
|
public IList<IEntityManager> EntityManagers { get; private set; }
|
|
public IEventScheduler Scheduler { get; private set; }
|
|
public IBlockRepository BlockRepository { get; private set; }
|
|
public IItemRepository ItemRepository { get; private set; }
|
|
public ICraftingRepository CraftingRepository { get; private set; }
|
|
public bool EnableClientLogging { get; set; }
|
|
|
|
private bool _BlockUpdatesEnabled = true;
|
|
private struct BlockUpdate
|
|
{
|
|
public Coordinates3D Coordinates;
|
|
public IWorld World;
|
|
}
|
|
private Queue<BlockUpdate> PendingBlockUpdates { get; set; }
|
|
public bool BlockUpdatesEnabled
|
|
{
|
|
get
|
|
{
|
|
return _BlockUpdatesEnabled;
|
|
}
|
|
set
|
|
{
|
|
_BlockUpdatesEnabled = value;
|
|
if (_BlockUpdatesEnabled)
|
|
{
|
|
ProcessBlockUpdates();
|
|
}
|
|
}
|
|
}
|
|
|
|
private Timer EnvironmentWorker;
|
|
private Thread NetworkWorker;
|
|
private TcpListener Listener;
|
|
private readonly PacketHandler[] PacketHandlers;
|
|
private IList<ILogProvider> LogProviders;
|
|
internal object ClientLock = new object();
|
|
private bool ShuttingDown = false;
|
|
|
|
public MultiplayerServer()
|
|
{
|
|
var reader = new PacketReader();
|
|
PacketReader = reader;
|
|
Clients = new List<IRemoteClient>();
|
|
NetworkWorker = new Thread(new ThreadStart(DoNetwork));
|
|
EnvironmentWorker = new Timer(DoEnvironment);
|
|
PacketHandlers = new PacketHandler[0x100];
|
|
Worlds = new List<IWorld>();
|
|
EntityManagers = new List<IEntityManager>();
|
|
LogProviders = new List<ILogProvider>();
|
|
Scheduler = new EventScheduler(this);
|
|
var blockRepository = new BlockRepository();
|
|
blockRepository.DiscoverBlockProviders();
|
|
BlockRepository = blockRepository;
|
|
var itemRepository = new ItemRepository();
|
|
itemRepository.DiscoverItemProviders();
|
|
ItemRepository = itemRepository;
|
|
var craftingRepository = new CraftingRepository();
|
|
craftingRepository.DiscoverRecipes();
|
|
CraftingRepository = craftingRepository;
|
|
PendingBlockUpdates = new Queue<BlockUpdate>();
|
|
EnableClientLogging = false;
|
|
|
|
reader.RegisterCorePackets();
|
|
Handlers.PacketHandlers.RegisterHandlers(this);
|
|
}
|
|
|
|
public void RegisterPacketHandler(byte packetId, PacketHandler handler)
|
|
{
|
|
PacketHandlers[packetId] = handler;
|
|
}
|
|
|
|
public void Start(IPEndPoint endPoint)
|
|
{
|
|
ShuttingDown = false;
|
|
Listener = new TcpListener(endPoint);
|
|
Listener.Start();
|
|
Listener.BeginAcceptTcpClient(AcceptClient, null);
|
|
Log(LogCategory.Notice, "Running TrueCraft server on {0}", endPoint);
|
|
NetworkWorker.Start();
|
|
EnvironmentWorker.Change(100, 1000 / 20);
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
ShuttingDown = true;
|
|
Listener.Stop();
|
|
foreach (var w in Worlds)
|
|
w.Save();
|
|
foreach (var c in Clients)
|
|
DisconnectClient(c);
|
|
}
|
|
|
|
public void AddWorld(IWorld world)
|
|
{
|
|
Worlds.Add(world);
|
|
world.BlockRepository = BlockRepository;
|
|
world.BlockChanged += HandleBlockChanged;
|
|
var manager = new EntityManager(this, world);
|
|
EntityManagers.Add(manager);
|
|
}
|
|
|
|
void HandleBlockChanged(object sender, BlockChangeEventArgs e)
|
|
{
|
|
for (int i = 0, ClientsCount = Clients.Count; i < ClientsCount; i++)
|
|
{
|
|
var client = (RemoteClient)Clients[i];
|
|
// TODO: Confirm that the client knows of this block
|
|
if (client.LoggedIn && client.World == sender)
|
|
{
|
|
client.QueuePacket(new BlockChangePacket(e.Position.X, (sbyte)e.Position.Y, e.Position.Z,
|
|
(sbyte)e.NewBlock.ID, (sbyte)e.NewBlock.Metadata));
|
|
}
|
|
}
|
|
PendingBlockUpdates.Enqueue(new BlockUpdate { Coordinates = e.Position, World = sender as IWorld });
|
|
ProcessBlockUpdates();
|
|
}
|
|
|
|
private void ProcessBlockUpdates()
|
|
{
|
|
if (!BlockUpdatesEnabled)
|
|
return;
|
|
var adjacent = new[]
|
|
{
|
|
Coordinates3D.Up, Coordinates3D.Down,
|
|
Coordinates3D.Left, Coordinates3D.Right,
|
|
Coordinates3D.Forwards, Coordinates3D.Backwards
|
|
};
|
|
while (PendingBlockUpdates.Count != 0)
|
|
{
|
|
var update = PendingBlockUpdates.Dequeue();
|
|
var source = update.World.GetBlockData(update.Coordinates);
|
|
foreach (var offset in adjacent)
|
|
{
|
|
var descriptor = update.World.GetBlockData(update.Coordinates + offset);
|
|
var provider = BlockRepository.GetBlockProvider(descriptor.ID);
|
|
if (provider != null)
|
|
provider.BlockUpdate(descriptor, source, this, update.World);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddLogProvider(ILogProvider provider)
|
|
{
|
|
LogProviders.Add(provider);
|
|
}
|
|
|
|
public void Log(LogCategory category, string text, params object[] parameters)
|
|
{
|
|
for (int i = 0, LogProvidersCount = LogProviders.Count; i < LogProvidersCount; i++)
|
|
{
|
|
var provider = LogProviders[i];
|
|
provider.Log(category, text, parameters);
|
|
}
|
|
}
|
|
|
|
public IEntityManager GetEntityManagerForWorld(IWorld world)
|
|
{
|
|
for (int i = 0; i < EntityManagers.Count; i++)
|
|
{
|
|
var manager = EntityManagers[i] as EntityManager;
|
|
if (manager.World == world)
|
|
return manager;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void SendMessage(string message, params object[] parameters)
|
|
{
|
|
var compiled = string.Format(message, parameters);
|
|
var parts = compiled.Split('\n');
|
|
foreach (var client in Clients)
|
|
{
|
|
foreach (var part in parts)
|
|
client.SendMessage(part);
|
|
}
|
|
Log(LogCategory.Notice, ChatColor.RemoveColors(compiled));
|
|
}
|
|
|
|
protected internal void OnChatMessageReceived(ChatMessageEventArgs e)
|
|
{
|
|
if (ChatMessageReceived != null)
|
|
ChatMessageReceived(this, e);
|
|
}
|
|
|
|
protected internal void OnPlayerJoined(PlayerJoinedEventArgs e)
|
|
{
|
|
if (PlayerJoined != null)
|
|
PlayerJoined(this, e);
|
|
}
|
|
|
|
private void LogPacket(IPacket packet, bool clientToServer)
|
|
{
|
|
for (int i = 0, LogProvidersCount = LogProviders.Count; i < LogProvidersCount; i++)
|
|
{
|
|
var provider = LogProviders[i];
|
|
packet.Log(provider, clientToServer);
|
|
}
|
|
}
|
|
|
|
private void DisconnectClient(IRemoteClient _client)
|
|
{
|
|
var client = (RemoteClient)_client;
|
|
if (client.LoggedIn)
|
|
{
|
|
SendMessage(ChatColor.Yellow + "{0} has left the server.", client.Username);
|
|
GetEntityManagerForWorld(client.World).DespawnEntity(client.Entity);
|
|
GetEntityManagerForWorld(client.World).FlushDespawns();
|
|
}
|
|
client.Save();
|
|
client.Disconnected = true;
|
|
}
|
|
|
|
private void AcceptClient(IAsyncResult result)
|
|
{
|
|
var tcpClient = Listener.EndAcceptTcpClient(result);
|
|
var client = new RemoteClient(this, tcpClient.GetStream());
|
|
lock (ClientLock)
|
|
Clients.Add(client);
|
|
Listener.BeginAcceptTcpClient(AcceptClient, null);
|
|
}
|
|
|
|
private void DoEnvironment(object discarded)
|
|
{
|
|
if (ShuttingDown)
|
|
return;
|
|
Scheduler.Update();
|
|
foreach (var manager in EntityManagers)
|
|
{
|
|
manager.Update();
|
|
}
|
|
}
|
|
|
|
private void DoNetwork()
|
|
{
|
|
while (true)
|
|
{
|
|
if (ShuttingDown)
|
|
return;
|
|
bool idle = true;
|
|
for (int i = 0; i < Clients.Count && i >= 0; i++)
|
|
{
|
|
RemoteClient client;
|
|
lock (ClientLock)
|
|
client = Clients[i] as RemoteClient;
|
|
while (client.PacketQueue.Count != 0)
|
|
{
|
|
idle = false;
|
|
try
|
|
{
|
|
IPacket packet;
|
|
while (!client.PacketQueue.TryDequeue(out packet))
|
|
;
|
|
LogPacket(packet, false);
|
|
PacketReader.WritePacket(client.MinecraftStream, packet);
|
|
client.MinecraftStream.BaseStream.Flush();
|
|
if (packet is DisconnectPacket)
|
|
{
|
|
DisconnectClient(client);
|
|
break;
|
|
}
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
Log(LogCategory.Debug, "Disconnecting client due to exception in network worker");
|
|
Log(LogCategory.Debug, e.ToString());
|
|
PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server."));
|
|
client.MinecraftStream.BaseStream.Flush();
|
|
DisconnectClient(client);
|
|
break;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(LogCategory.Debug, "Disconnecting client due to exception in network worker");
|
|
Log(LogCategory.Debug, e.ToString());
|
|
DisconnectClient(client);
|
|
break;
|
|
}
|
|
}
|
|
if (client.Disconnected)
|
|
{
|
|
lock (ClientLock)
|
|
Clients.RemoveAt(i);
|
|
break;
|
|
}
|
|
const long maxTicks = 100000 * 200; // 200ms
|
|
var start = DateTime.Now;
|
|
while (client.DataAvailable && (DateTime.Now.Ticks - start.Ticks) < maxTicks)
|
|
{
|
|
idle = false;
|
|
try
|
|
{
|
|
var packet = PacketReader.ReadPacket(client.MinecraftStream);
|
|
client.LastSuccessfulPacket = packet;
|
|
LogPacket(packet, true);
|
|
if (PacketHandlers[packet.ID] != null)
|
|
PacketHandlers[packet.ID](packet, client, this);
|
|
else
|
|
client.Log("Unhandled packet {0}", packet.GetType().Name);
|
|
}
|
|
catch (PlayerDisconnectException)
|
|
{
|
|
DisconnectClient(client);
|
|
break;
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
Log(LogCategory.Debug, "Disconnecting client due to exception in network worker");
|
|
Log(LogCategory.Debug, e.ToString());
|
|
DisconnectClient(client);
|
|
break;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(LogCategory.Debug, "Disconnecting client due to exception in network worker");
|
|
Log(LogCategory.Debug, e.ToString());
|
|
try
|
|
{
|
|
PacketReader.WritePacket(client.MinecraftStream, new DisconnectPacket("An exception has occured on the server."));
|
|
client.MinecraftStream.BaseStream.Flush();
|
|
}
|
|
catch { /* Silently ignore, by now it's too late */ }
|
|
DisconnectClient(client);
|
|
break;
|
|
}
|
|
}
|
|
if (idle)
|
|
Thread.Sleep(100);
|
|
if (client.Disconnected)
|
|
{
|
|
lock (ClientLock)
|
|
Clients.RemoveAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |