TrueCraft/TrueCraft.Client/MultiplayerClient.cs
Drew DeVault facf8ce2c6 Send players their health on log in
Health is already being saved to disk with their NBT file, so this just
involves sending it to them when they log in.
2015-10-08 08:19:15 -04:00

375 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;
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 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 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;
}
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);
}
#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);
}
}
}