This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
TrueCraft/TrueCraft/MultiplayerServer.cs
2015-06-20 19:43:28 -07:00

331 lines
11 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 System.Linq;
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;
using TrueCraft.Core.Logic;
namespace TrueCraft
{
public class MultiplayerServer : IMultiplayerServer, IDisposable
{
public event EventHandler<ChatMessageEventArgs> ChatMessageReceived;
public event EventHandler<PlayerJoinedQuitEventArgs> PlayerJoined;
public event EventHandler<PlayerJoinedQuitEventArgs> PlayerQuit;
public IAccessConfiguration AccessConfiguration { get; internal set; }
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; }
public IPEndPoint EndPoint { get; private 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 TcpListener Listener;
private readonly PacketHandler[] PacketHandlers;
private IList<ILogProvider> LogProviders;
internal object ClientLock = new object();
public bool ShuttingDown { get; private set; }
public MultiplayerServer()
{
var reader = new PacketReader();
PacketReader = reader;
Clients = new List<IRemoteClient>();
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;
AccessConfiguration = Configuration.LoadConfiguration<AccessConfiguration>("access.yaml");
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();
EndPoint = (IPEndPoint)Listener.LocalEndpoint;
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += AcceptClient;
if (!Listener.Server.AcceptAsync(args))
AcceptClient(this, args);
Log(LogCategory.Notice, "Running TrueCraft server on {0}", EndPoint);
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(PlayerJoinedQuitEventArgs e)
{
if (PlayerJoined != null)
PlayerJoined(this, e);
}
protected internal void OnPlayerQuit(PlayerJoinedQuitEventArgs e)
{
if (PlayerQuit != null)
PlayerQuit(this, e);
}
public void DisconnectClient(IRemoteClient _client)
{
var client = (RemoteClient)_client;
lock (ClientLock)
{
Clients.Remove(client);
}
if (client.Disconnected)
return;
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.Disconnect();
OnPlayerQuit(new PlayerJoinedQuitEventArgs(client));
client.Dispose();
}
private void AcceptClient(object sender, SocketAsyncEventArgs args)
{
try
{
var client = new RemoteClient(this, PacketReader, PacketHandlers, args.AcceptSocket);
lock (ClientLock)
Clients.Add(client);
}
catch
{
// Who cares
}
finally
{
args.AcceptSocket = null;
if (!ShuttingDown && !Listener.Server.AcceptAsync(args))
AcceptClient(this, args);
}
}
private void DoEnvironment(object discarded)
{
if (ShuttingDown)
return;
Scheduler.Update();
foreach (var manager in EntityManagers)
{
manager.Update();
}
}
public bool PlayerIsWhitelisted(string client)
{
return AccessConfiguration.Whitelist.Contains(client, StringComparer.CurrentCultureIgnoreCase);
}
public bool PlayerIsBlacklisted(string client)
{
return AccessConfiguration.Blacklist.Contains(client, StringComparer.CurrentCultureIgnoreCase);
}
public bool PlayerIsOp(string client)
{
return AccessConfiguration.Oplist.Contains(client, StringComparer.CurrentCultureIgnoreCase);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Stop();
}
}
~MultiplayerServer()
{
Dispose(false);
}
}
}