TrueCraft/TrueCraft.Client/MultiplayerClient.cs
2016-04-06 20:36:48 -04:00

389 lines
11 KiB
C#

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Concurrent;
using TrueCraft.API.Networking;
using System.Threading;
using TrueCraft.Core.Networking;
using System.Linq;
using TrueCraft.Core.Networking.Packets;
using TrueCraft.Client.Events;
using TrueCraft.Core.Logic;
using TrueCraft.API.Entities;
using TrueCraft.API;
using System.ComponentModel;
using System.IO;
using TrueCraft.Core;
using TrueCraft.API.Physics;
using TrueCraft.Core.Physics;
using TrueCraft.Core.Windows;
using TrueCraft.API.Windows;
using TrueCraft.API.Logic;
using TrueCraft.API.World;
namespace TrueCraft.Client
{
public delegate void PacketHandler(IPacket packet, MultiplayerClient client);
public class MultiplayerClient : IAABBEntity, INotifyPropertyChanged, IDisposable // TODO: Make IMultiplayerClient and so on
{
public event EventHandler<ChatMessageEventArgs> ChatMessage;
public event EventHandler<ChunkEventArgs> ChunkModified;
public event EventHandler<ChunkEventArgs> ChunkLoaded;
public event EventHandler<ChunkEventArgs> ChunkUnloaded;
public event EventHandler<BlockChangeEventArgs> BlockChanged;
public event PropertyChangedEventHandler PropertyChanged;
private long connected;
private int hotbarSelection;
public TrueCraftUser User { get; set; }
public ReadOnlyWorld World { get; private set; }
public PhysicsEngine Physics { get; set; }
public bool LoggedIn { get; internal set; }
public int EntityID { get; internal set; }
public InventoryWindow Inventory { get; set; }
public int Health { get; set; }
public IWindow CurrentWindow { get; set; }
public ICraftingRepository CraftingRepository { get; set; }
public bool Connected
{
get
{
return Interlocked.Read(ref connected) == 1;
}
}
public int HotbarSelection
{
get { return hotbarSelection; }
set
{
hotbarSelection = value;
QueuePacket(new ChangeHeldItemPacket() { Slot = (short)value });
}
}
private TcpClient Client { get; set; }
private IMinecraftStream Stream { get; set; }
private PacketReader PacketReader { get; set; }
private readonly PacketHandler[] PacketHandlers;
private SemaphoreSlim sem = new SemaphoreSlim(1, 1);
private readonly CancellationTokenSource cancel;
private SocketAsyncEventArgsPool SocketPool { get; set; }
public MultiplayerClient(TrueCraftUser user)
{
User = user;
Client = new TcpClient();
PacketReader = new PacketReader();
PacketReader.RegisterCorePackets();
PacketHandlers = new PacketHandler[0x100];
Handlers.PacketHandlers.RegisterHandlers(this);
World = new ReadOnlyWorld();
Inventory = new InventoryWindow(null);
var repo = new BlockRepository();
repo.DiscoverBlockProviders();
World.World.BlockRepository = repo;
World.World.ChunkProvider = new EmptyGenerator();
Physics = new PhysicsEngine(World.World, repo);
SocketPool = new SocketAsyncEventArgsPool(100, 200, 65536);
connected = 0;
cancel = new CancellationTokenSource();
Health = 20;
var crafting = new CraftingRepository();
CraftingRepository = crafting;
crafting.DiscoverRecipes();
}
public void RegisterPacketHandler(byte packetId, PacketHandler handler)
{
PacketHandlers[packetId] = handler;
}
public void Connect(IPEndPoint endPoint)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += Connection_Completed;
args.RemoteEndPoint = endPoint;
if (!Client.Client.ConnectAsync(args))
Connection_Completed(this, args);
}
private void Connection_Completed(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Interlocked.CompareExchange(ref connected, 1, 0);
Physics.AddEntity(this);
StartReceive();
QueuePacket(new HandshakePacket(User.Username));
}
else
{
throw new Exception("Could not connect to server!");
}
}
public void Disconnect()
{
if (!Connected)
return;
QueuePacket(new DisconnectPacket("Disconnecting"));
Interlocked.CompareExchange(ref connected, 0, 1);
}
public void SendMessage(string message)
{
var parts = message.Split('\n');
foreach (var part in parts)
QueuePacket(new ChatMessagePacket(part));
}
public void QueuePacket(IPacket packet)
{
if (!Connected || (Client != null && !Client.Connected))
return;
using (MemoryStream writeStream = new MemoryStream())
{
using (MinecraftStream ms = new MinecraftStream(writeStream))
{
ms.WriteUInt8(packet.ID);
packet.WritePacket(ms);
}
byte[] buffer = writeStream.ToArray();
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.UserToken = packet;
args.Completed += OperationCompleted;
args.SetBuffer(buffer, 0, buffer.Length);
if (Client != null && !Client.Client.SendAsync(args))
OperationCompleted(this, args);
}
}
private void StartReceive()
{
SocketAsyncEventArgs args = SocketPool.Get();
args.Completed += OperationCompleted;
if (!Client.Client.ReceiveAsync(args))
OperationCompleted(this, args);
}
private void OperationCompleted(object sender, SocketAsyncEventArgs e)
{
e.Completed -= OperationCompleted;
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessNetwork(e);
SocketPool.Add(e);
break;
case SocketAsyncOperation.Send:
IPacket packet = e.UserToken as IPacket;
if (packet is DisconnectPacket)
{
Client.Client.Shutdown(SocketShutdown.Send);
Client.Close();
cancel.Cancel();
}
e.SetBuffer(null, 0, 0);
break;
}
}
private void ProcessNetwork(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
{
SocketAsyncEventArgs newArgs = SocketPool.Get();
newArgs.Completed += OperationCompleted;
if (Client != null && !Client.Client.ReceiveAsync(newArgs))
OperationCompleted(this, newArgs);
try
{
sem.Wait(cancel.Token);
}
catch (OperationCanceledException)
{
return;
}
var packets = PacketReader.ReadPackets(this, e.Buffer, e.Offset, e.BytesTransferred, false);
foreach (IPacket packet in packets)
{
if (PacketHandlers[packet.ID] != null)
PacketHandlers[packet.ID](packet, this);
}
if (sem != null)
sem.Release();
}
else
{
Disconnect();
}
}
protected internal void OnChatMessage(ChatMessageEventArgs e)
{
if (ChatMessage != null) ChatMessage(this, e);
}
protected internal void OnChunkLoaded(ChunkEventArgs e)
{
if (ChunkLoaded != null) ChunkLoaded(this, e);
}
protected internal void OnChunkUnloaded(ChunkEventArgs e)
{
if (ChunkUnloaded != null) ChunkUnloaded(this, e);
}
protected internal void OnChunkModified(ChunkEventArgs e)
{
if (ChunkModified != null) ChunkModified(this, e);
}
protected internal void OnBlockChanged(BlockChangeEventArgs e)
{
if (BlockChanged != null) BlockChanged(this, e);
}
#region IAABBEntity implementation
public const double Width = 0.6;
public const double Height = 1.62;
public const double Depth = 0.6;
public void TerrainCollision(Vector3 collisionPoint, Vector3 collisionDirection)
{
// This space intentionally left blank
}
public BoundingBox BoundingBox
{
get
{
var pos = Position - new Vector3(Width / 2, 0, Depth / 2);
return new BoundingBox(pos, pos + Size);
}
}
public Size Size
{
get { return new Size(Width, Height, Depth); }
}
#endregion
#region IPhysicsEntity implementation
public bool BeginUpdate()
{
return true;
}
public void EndUpdate(Vector3 newPosition)
{
Position = newPosition;
}
public float Yaw { get; set; }
public float Pitch { get; set; }
internal Vector3 _Position;
public Vector3 Position
{
get
{
return _Position;
}
set
{
if (_Position != value)
{
QueuePacket(new PlayerPositionAndLookPacket(value.X, value.Y, value.Y + Height,
value.Z, Yaw, Pitch, false));
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Position"));
}
_Position = value;
}
}
public Vector3 Velocity { get; set; }
public float AccelerationDueToGravity
{
get
{
return 1.6f;
}
}
public float Drag
{
get
{
return 0.40f;
}
}
public float TerminalVelocity
{
get
{
return 78.4f;
}
}
#endregion
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Disconnect();
sem.Dispose();
}
sem = null;
}
~MultiplayerClient()
{
Dispose(false);
}
}
}