ClassiCube/ClassicalSharp/Network/NetworkProcessor.Original.cs

351 lines
12 KiB
C#

// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
using System;
using ClassicalSharp.Gui;
using ClassicalSharp.Entities;
using OpenTK;
#if __MonoCS__
using Ionic.Zlib;
#else
using System.IO.Compression;
#endif
namespace ClassicalSharp.Network {
public partial class NetworkProcessor : INetworkProcessor {
#region Writing
public override void SendChat( string text, bool partial ) {
if( String.IsNullOrEmpty( text ) ) return;
byte payload = !ServerSupportsPartialMessages ? (byte)0xFF:
partial ? (byte)1 : (byte)0;
writer.WriteUInt8( (byte)Opcode.Message );
writer.WriteUInt8( payload );
writer.WriteString( text );
SendPacket();
}
public override void SendPosition( Vector3 pos, float yaw, float pitch ) {
byte payload = cpe.sendHeldBlock ? (byte)game.Inventory.HeldBlock : (byte)0xFF;
MakePositionPacket( pos, yaw, pitch, payload );
SendPacket();
}
public override void SendSetBlock( int x, int y, int z, bool place, byte block ) {
writer.WriteUInt8( (byte)Opcode.SetBlockClient );
writer.WriteInt16( (short)x );
writer.WriteInt16( (short)y );
writer.WriteInt16( (short)z );
writer.WriteUInt8( place ? (byte)1 : (byte)0 );
writer.WriteUInt8( block );
SendPacket();
}
void MakeLoginPacket( string username, string verKey ) {
writer.WriteUInt8( (byte)Opcode.Handshake );
writer.WriteUInt8( 7 ); // protocol version
writer.WriteString( username );
writer.WriteString( verKey );
byte payload = game.UseCPE ? (byte)0x42 : (byte)0x00;
writer.WriteUInt8( payload );
}
void MakePositionPacket( Vector3 pos, float yaw, float pitch, byte payload ) {
writer.WriteUInt8( (byte)Opcode.EntityTeleport );
writer.WriteUInt8( payload ); // held block when using HeldBlock, otherwise just 255
writer.WriteInt16( (short)(pos.X * 32) );
writer.WriteInt16( (short)((int)(pos.Y * 32) + 51) );
writer.WriteInt16( (short)(pos.Z * 32) );
writer.WriteUInt8( (byte)Utils.DegreesToPacked( yaw ) );
writer.WriteUInt8( (byte)Utils.DegreesToPacked( pitch ) );
}
#endregion
#region Reading
DateTime receiveStart;
DeflateStream gzipStream;
GZipHeaderReader gzipHeader;
int mapSizeIndex, mapIndex;
byte[] mapSize = new byte[4], map;
FixedBufferStream gzippedMap;
internal void HandleHandshake() {
byte protocolVer = reader.ReadUInt8();
ServerName = reader.ReadCp437String();
ServerMotd = reader.ReadCp437String();
receivedFirstPosition = false;
game.LocalPlayer.Hacks.SetUserType( reader.ReadUInt8() );
game.LocalPlayer.Hacks.ParseHackFlags( ServerName, ServerMotd );
game.LocalPlayer.CheckHacksConsistency();
game.Events.RaiseHackPermissionsChanged();
}
internal void HandlePing() { }
internal void HandleLevelInit() {
if( gzipStream != null )
return;
game.World.Reset();
prevScreen = game.activeScreen;
if( prevScreen is LoadingMapScreen )
prevScreen = null;
prevCursorVisible = game.CursorVisible;
game.SetNewScreen( new LoadingMapScreen( game, ServerName, ServerMotd ), false );
if( ServerMotd.Contains( "cfg=" ) ) {
ReadWomConfigurationAsync();
}
receivedFirstPosition = false;
gzipHeader = new GZipHeaderReader();
// Workaround because built in mono stream assumes that the end of stream
// has been reached the first time a read call returns 0. (MS.NET doesn't)
#if __MonoCS__
gzipStream = new DeflateStream( gzippedMap, true );
#else
gzipStream = new DeflateStream( gzippedMap, CompressionMode.Decompress );
if( OpenTK.Configuration.RunningOnMono ) {
throw new InvalidOperationException( "You must compile ClassicalSharp with __MonoCS__ defined " +
"to run on Mono, due to a limitation in Mono." );
}
#endif
mapSizeIndex = 0;
mapIndex = 0;
receiveStart = DateTime.UtcNow;
}
internal void HandleLevelDataChunk() {
// Workaround for some servers that send LevelDataChunk before LevelInit
// due to their async packet sending behaviour.
if( gzipStream == null )
HandleLevelInit();
int usedLength = reader.ReadInt16();
gzippedMap.Position = 0;
gzippedMap.Offset = reader.index;
gzippedMap.SetLength( usedLength );
if( gzipHeader.done || gzipHeader.ReadHeader( gzippedMap ) ) {
if( mapSizeIndex < 4 ) {
mapSizeIndex += gzipStream.Read( mapSize, mapSizeIndex, 4 - mapSizeIndex );
}
if( mapSizeIndex == 4 ) {
if( map == null ) {
int size = mapSize[0] << 24 | mapSize[1] << 16 | mapSize[2] << 8 | mapSize[3];
map = new byte[size];
}
mapIndex += gzipStream.Read( map, mapIndex, map.Length - mapIndex );
}
}
reader.Skip( 1025 ); // also skip progress since we calculate client side
float progress = map == null ? 0 : (float)mapIndex / map.Length;
game.WorldEvents.RaiseMapLoading( progress );
}
internal void HandleLevelFinalise() {
game.SetNewScreen( null );
game.activeScreen = prevScreen;
if( prevScreen != null && prevCursorVisible != game.CursorVisible )
game.CursorVisible = prevCursorVisible;
prevScreen = null;
int mapWidth = reader.ReadInt16();
int mapHeight = reader.ReadInt16();
int mapLength = reader.ReadInt16();
double loadingMs = ( DateTime.UtcNow - receiveStart ).TotalMilliseconds;
Utils.LogDebug( "map loading took:" + loadingMs );
game.World.SetNewMap( map, mapWidth, mapHeight, mapLength );
game.WorldEvents.RaiseOnNewMapLoaded();
map = null;
gzipStream.Dispose();
if( sendWomId && !sentWomId ) {
SendChat( "/womid WoMClient-2.0.7", false );
sentWomId = true;
}
gzipStream = null;
GC.Collect();
}
internal void HandleSetBlock() {
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadInt16();
byte type = reader.ReadUInt8();
#if DEBUG_BLOCKS
if( game.World.IsNotLoaded )
Utils.LogDebug( "Server tried to update a block while still sending us the map!" );
else if( !game.World.IsValidPos( x, y, z ) )
Utils.LogDebug( "Server tried to update a block at an invalid position!" );
else
game.UpdateBlock( x, y, z, type );
#else
if( !game.World.IsNotLoaded && game.World.IsValidPos( x, y, z ) )
game.UpdateBlock( x, y, z, type );
#endif
}
bool[] needRemoveNames;
internal void HandleAddEntity() {
byte entityId = reader.ReadUInt8();
string name = reader.ReadAsciiString();
name = Utils.RemoveEndPlus( name );
AddEntity( entityId, name, name, true );
// Workaround for LegendCraft as it declares it supports ExtPlayerList but
// doesn't send ExtAddPlayerName packets. So we add a special case here, even
// though it is technically against the specification.
if( UsingExtPlayerList ) {
AddCpeInfo( entityId, name, name, "Players", 0 );
if( needRemoveNames == null )
needRemoveNames = new bool[EntityList.MaxCount];
needRemoveNames[entityId] = true;
}
}
internal void HandleEntityTeleport() {
byte entityId = reader.ReadUInt8();
ReadAbsoluteLocation( entityId, true );
}
internal void HandleRelPosAndOrientationUpdate() {
byte playerId = reader.ReadUInt8();
float x = reader.ReadInt8() / 32f;
float y = reader.ReadInt8() / 32f;
float z = reader.ReadInt8() / 32f;
float yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
LocationUpdate update = LocationUpdate.MakePosAndOri( x, y, z, yaw, pitch, true );
UpdateLocation( playerId, update, true );
}
internal void HandleRelPositionUpdate() {
byte playerId = reader.ReadUInt8();
float x = reader.ReadInt8() / 32f;
float y = reader.ReadInt8() / 32f;
float z = reader.ReadInt8() / 32f;
LocationUpdate update = LocationUpdate.MakePos( x, y, z, true );
UpdateLocation( playerId, update, true );
}
internal void HandleOrientationUpdate() {
byte playerId = reader.ReadUInt8();
float yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
LocationUpdate update = LocationUpdate.MakeOri( yaw, pitch );
UpdateLocation( playerId, update, true );
}
internal void HandleRemoveEntity() {
byte entityId = reader.ReadUInt8();
RemoveEntity( entityId );
}
internal void HandleMessage() {
byte messageType = reader.ReadUInt8();
string text = reader.ReadChatString( ref messageType );
// Original vanilla server uses player ids in message types, 255 for server messages.
if( !cpe.useMessageTypes ) {
if( messageType == 0xFF ) text = "&e" + text;
messageType = (byte)MessageType.Normal;
}
if( !text.StartsWith("^detail.user", StringComparison.OrdinalIgnoreCase ) )
game.Chat.Add( text, (MessageType)messageType );
}
internal void HandleKick() {
string reason = reader.ReadCp437String();
game.Disconnect( "&eLost connection to the server", reason );
Dispose();
}
internal void HandleSetPermission() {
game.LocalPlayer.Hacks.SetUserType( reader.ReadUInt8() );
}
void AddEntity( byte entityId, string displayName, string skinName, bool readPosition ) {
skinName = Utils.StripColours( skinName );
if( entityId != 0xFF ) {
Player oldPlayer = game.Players[entityId];
if( oldPlayer != null ) {
game.EntityEvents.RaiseRemoved( entityId );
oldPlayer.Despawn();
}
game.Players[entityId] = new NetPlayer( displayName, skinName, game, entityId );
game.EntityEvents.RaiseAdded( entityId );
} else {
// Server is only allowed to change our own name colours.
if( Utils.StripColours( displayName ) != game.Username )
displayName = game.Username;
game.LocalPlayer.DisplayName = displayName;
game.LocalPlayer.SkinName = skinName;
game.LocalPlayer.UpdateName();
}
string identifier = game.Players[entityId].SkinIdentifier;
game.AsyncDownloader.DownloadSkin( identifier, skinName );
if( !readPosition ) return;
ReadAbsoluteLocation( entityId, false );
if( entityId == 0xFF ) {
LocalPlayer p = game.LocalPlayer;
p.Spawn = p.Position;
p.SpawnYaw = p.HeadYawDegrees;
p.SpawnPitch = p.PitchDegrees;
}
}
void RemoveEntity( byte entityId ) {
Player player = game.Players[entityId];
if( player == null ) return;
if( entityId != 0xFF ) {
game.EntityEvents.RaiseRemoved( entityId );
player.Despawn();
game.Players[entityId] = null;
}
// See comment about LegendCraft in HandleAddEntity
if( needRemoveNames != null && needRemoveNames[entityId] ) {
game.EntityEvents.RaiseCpeListInfoRemoved( entityId );
game.CpePlayersList[entityId] = null;
needRemoveNames[entityId] = false;
}
}
void ReadAbsoluteLocation( byte playerId, bool interpolate ) {
float x = reader.ReadInt16() / 32f;
float y = ( reader.ReadInt16() - 51 ) / 32f; // We have to do this.
if( playerId == 255 ) y += 22/32f;
float z = reader.ReadInt16() / 32f;
float yaw = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
float pitch = (float)Utils.PackedToDegrees( reader.ReadUInt8() );
if( playerId == 0xFF ) {
receivedFirstPosition = true;
}
LocationUpdate update = LocationUpdate.MakePosAndOri( x, y, z, yaw, pitch, false );
UpdateLocation( playerId, update, interpolate );
}
void UpdateLocation( byte playerId, LocationUpdate update, bool interpolate ) {
Player player = game.Players[playerId];
if( player != null ) {
player.SetLocation( update, interpolate );
}
}
#endregion
}
}