mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-10-06 12:37:28 -04:00
415 lines
14 KiB
C#
415 lines
14 KiB
C#
// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
|
|
using System;
|
|
using ClassicalSharp.Entities;
|
|
using ClassicalSharp.Hotkeys;
|
|
using ClassicalSharp.Map;
|
|
using ClassicalSharp.Textures;
|
|
using OpenTK.Input;
|
|
|
|
namespace ClassicalSharp.Network.Protocols {
|
|
|
|
/// <summary> Implements the packets for classic protocol extension. </summary>
|
|
public sealed class CPEProtocol : IProtocol {
|
|
|
|
public CPEProtocol( Game game ) : base( game ) { }
|
|
|
|
public override void Init() {
|
|
if( !game.UseCPE ) return;
|
|
net.Set( Opcode.CpeExtInfo, HandleExtInfo, 67 );
|
|
net.Set( Opcode.CpeExtEntry, HandleExtEntry, 69 );
|
|
net.Set( Opcode.CpeSetClickDistance, HandleSetClickDistance, 3 );
|
|
net.Set( Opcode.CpeCustomBlockSupportLevel, HandleCustomBlockSupportLevel, 2 );
|
|
net.Set( Opcode.CpeHoldThis, HandleHoldThis, 3 );
|
|
net.Set( Opcode.CpeSetTextHotkey, HandleSetTextHotkey, 134 );
|
|
|
|
net.Set( Opcode.CpeExtAddPlayerName, HandleExtAddPlayerName, 196 );
|
|
net.Set( Opcode.CpeExtAddEntity, HandleExtAddEntity, 130 );
|
|
net.Set( Opcode.CpeExtRemovePlayerName, HandleExtRemovePlayerName, 3 );
|
|
|
|
net.Set( Opcode.CpeEnvColours, HandleEnvColours, 8 );
|
|
net.Set( Opcode.CpeMakeSelection, HandleMakeSelection, 86 );
|
|
net.Set( Opcode.CpeRemoveSelection, HandleRemoveSelection, 2 );
|
|
net.Set( Opcode.CpeSetBlockPermission, HandleSetBlockPermission, 4 );
|
|
net.Set( Opcode.CpeChangeModel, HandleChangeModel, 66 );
|
|
net.Set( Opcode.CpeEnvSetMapApperance, HandleEnvSetMapAppearance, 69 );
|
|
net.Set( Opcode.CpeEnvWeatherType, HandleEnvWeatherType, 2 );
|
|
net.Set( Opcode.CpeHackControl, HandleHackControl, 8 );
|
|
net.Set( Opcode.CpeExtAddEntity2, HandleExtAddEntity2, 138 );
|
|
|
|
net.Set( Opcode.CpeBulkBlockUpdate, HandleBulkBlockUpdate, 1282 );
|
|
net.Set( Opcode.CpeSetTextColor, HandleSetTextColor, 6 );
|
|
net.Set( Opcode.CpeSetMapEnvUrl, HandleSetMapEnvUrl, 65 );
|
|
net.Set( Opcode.CpeSetMapEnvProperty, HandleSetMapEnvProperty, 6 );
|
|
}
|
|
|
|
#region Read
|
|
void HandleExtInfo() {
|
|
string appName = reader.ReadAsciiString();
|
|
game.Chat.Add( "Server software: " + appName );
|
|
if( Utils.CaselessStarts( appName, "D3 server" ) )
|
|
net.cpeData.needD3Fix = true;
|
|
|
|
// Workaround for MCGalaxy that send ExtEntry sync but ExtInfoAsync. This means
|
|
// ExtEntry may sometimes arrive before ExtInfo, and thus we have to use += instead of =
|
|
net.cpeData.ServerExtensionsCount += reader.ReadInt16();
|
|
SendCpeExtInfoReply();
|
|
}
|
|
|
|
void HandleExtEntry() {
|
|
string extName = reader.ReadAsciiString();
|
|
int extVersion = reader.ReadInt32();
|
|
Utils.LogDebug( "cpe ext: {0}, {1}", extName, extVersion );
|
|
|
|
net.cpeData.HandleEntry( extName, extVersion, net );
|
|
SendCpeExtInfoReply();
|
|
}
|
|
|
|
void HandleSetClickDistance() {
|
|
game.LocalPlayer.ReachDistance = reader.ReadInt16() / 32f;
|
|
}
|
|
|
|
void HandleCustomBlockSupportLevel() {
|
|
byte supportLevel = reader.ReadUInt8();
|
|
SendCustomBlockSupportLevel( 1 );
|
|
game.UseCPEBlocks = true;
|
|
|
|
if( supportLevel == 1 ) {
|
|
game.Events.RaiseBlockPermissionsChanged();
|
|
} else {
|
|
Utils.LogDebug( "Server's block support level is {0}, this client only supports level 1.", supportLevel );
|
|
Utils.LogDebug( "You won't be able to see or use blocks from levels above level 1" );
|
|
}
|
|
}
|
|
|
|
void HandleHoldThis() {
|
|
byte blockType = reader.ReadUInt8();
|
|
bool canChange = reader.ReadUInt8() == 0;
|
|
game.Inventory.CanChangeHeldBlock = true;
|
|
game.Inventory.HeldBlock = blockType;
|
|
game.Inventory.CanChangeHeldBlock = canChange;
|
|
}
|
|
|
|
void HandleSetTextHotkey() {
|
|
string label = reader.ReadAsciiString();
|
|
string action = reader.ReadCp437String();
|
|
int keyCode = reader.ReadInt32();
|
|
byte keyMods = reader.ReadUInt8();
|
|
|
|
#if !ANDROID
|
|
if( keyCode < 0 || keyCode > 255 ) return;
|
|
Key key = LwjglToKey.Map[keyCode];
|
|
if( key == Key.Unknown ) return;
|
|
|
|
Utils.LogDebug( "CPE Hotkey added: " + key + "," + keyMods + " : " + action );
|
|
if( action == "" ) {
|
|
game.InputHandler.Hotkeys.RemoveHotkey( key, keyMods );
|
|
} else if( action[action.Length - 1] == '◙' ) { // graphical form of \n
|
|
action = action.Substring( 0, action.Length - 1 );
|
|
game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, false );
|
|
} else { // more input needed by user
|
|
game.InputHandler.Hotkeys.AddHotkey( key, keyMods, action, true );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void HandleExtAddPlayerName() {
|
|
short id = reader.ReadInt16();
|
|
string playerName = Utils.StripColours( reader.ReadAsciiString() );
|
|
playerName = Utils.RemoveEndPlus( playerName );
|
|
string listName = reader.ReadAsciiString();
|
|
listName = Utils.RemoveEndPlus( listName );
|
|
string groupName = reader.ReadAsciiString();
|
|
byte groupRank = reader.ReadUInt8();
|
|
|
|
// Workaround for some servers that don't cast signed bytes to unsigned, before converting them to shorts.
|
|
if( id < 0 ) id += 256;
|
|
if( id >= 0 && id <= 255 )
|
|
net.AddTablistEntry( (byte)id, playerName, listName, groupName, groupRank );
|
|
}
|
|
|
|
void HandleExtAddEntity() {
|
|
byte id = reader.ReadUInt8();
|
|
string displayName = reader.ReadAsciiString();
|
|
string skinName = reader.ReadAsciiString();
|
|
net.CheckName( id, ref displayName, ref skinName );
|
|
net.AddEntity( id, displayName, skinName, false );
|
|
}
|
|
|
|
void HandleExtRemovePlayerName() {
|
|
short id = reader.ReadInt16();
|
|
// Workaround for some servers that don't cast signed bytes to unsigned, before converting them to shorts.
|
|
if( id < 0 ) id += 256;
|
|
|
|
if( id >= 0 && id <= 255 ) {
|
|
game.EntityEvents.RaiseTabEntryRemoved( (byte)id );
|
|
game.TabList.Entries[id] = null;
|
|
}
|
|
}
|
|
|
|
void HandleMakeSelection() {
|
|
byte selectionId = reader.ReadUInt8();
|
|
string label = reader.ReadAsciiString();
|
|
short startX = reader.ReadInt16();
|
|
short startY = reader.ReadInt16();
|
|
short startZ = reader.ReadInt16();
|
|
short endX = reader.ReadInt16();
|
|
short endY = reader.ReadInt16();
|
|
short endZ = reader.ReadInt16();
|
|
|
|
byte r = (byte)reader.ReadInt16();
|
|
byte g = (byte)reader.ReadInt16();
|
|
byte b = (byte)reader.ReadInt16();
|
|
byte a = (byte)reader.ReadInt16();
|
|
|
|
Vector3I p1 = Vector3I.Min( startX, startY, startZ, endX, endY, endZ );
|
|
Vector3I p2 = Vector3I.Max( startX, startY, startZ, endX, endY, endZ );
|
|
FastColour col = new FastColour( r, g, b, a );
|
|
game.SelectionManager.AddSelection( selectionId, p1, p2, col );
|
|
}
|
|
|
|
void HandleRemoveSelection() {
|
|
byte selectionId = reader.ReadUInt8();
|
|
game.SelectionManager.RemoveSelection( selectionId );
|
|
}
|
|
|
|
void HandleEnvColours() {
|
|
byte variable = reader.ReadUInt8();
|
|
short red = reader.ReadInt16();
|
|
short green = reader.ReadInt16();
|
|
short blue = reader.ReadInt16();
|
|
bool invalid = red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255;
|
|
FastColour col = new FastColour( red, green, blue );
|
|
|
|
if( variable == 0 ) {
|
|
game.World.Env.SetSkyColour( invalid ? WorldEnv.DefaultSkyColour : col );
|
|
} else if( variable == 1 ) {
|
|
game.World.Env.SetCloudsColour( invalid ? WorldEnv.DefaultCloudsColour : col );
|
|
} else if( variable == 2 ) {
|
|
game.World.Env.SetFogColour( invalid ? WorldEnv.DefaultFogColour : col );
|
|
} else if( variable == 3 ) {
|
|
game.World.Env.SetShadowlight( invalid ? WorldEnv.DefaultShadowlight : col );
|
|
} else if( variable == 4 ) {
|
|
game.World.Env.SetSunlight( invalid ? WorldEnv.DefaultSunlight : col );
|
|
}
|
|
}
|
|
|
|
void HandleSetBlockPermission() {
|
|
byte blockId = reader.ReadUInt8();
|
|
bool canPlace = reader.ReadUInt8() != 0;
|
|
bool canDelete = reader.ReadUInt8() != 0;
|
|
Inventory inv = game.Inventory;
|
|
|
|
if( blockId == 0 ) {
|
|
int count = game.UseCPEBlocks ? Block.CpeCount : Block.OriginalCount;
|
|
for( int i = 1; i < count; i++ ) {
|
|
inv.CanPlace.SetNotOverridable( canPlace, i );
|
|
inv.CanDelete.SetNotOverridable( canDelete, i );
|
|
}
|
|
} else {
|
|
inv.CanPlace.SetNotOverridable( canPlace, blockId );
|
|
inv.CanDelete.SetNotOverridable( canDelete, blockId );
|
|
}
|
|
game.Events.RaiseBlockPermissionsChanged();
|
|
}
|
|
|
|
void HandleChangeModel() {
|
|
byte playerId = reader.ReadUInt8();
|
|
string modelName = Utils.ToLower( reader.ReadAsciiString() );
|
|
Player player = game.Entities[playerId];
|
|
if( player != null ) player.SetModel( modelName );
|
|
}
|
|
|
|
void HandleEnvSetMapAppearance() {
|
|
HandleSetMapEnvUrl();
|
|
game.World.Env.SetSidesBlock( reader.ReadUInt8() );
|
|
game.World.Env.SetEdgeBlock( reader.ReadUInt8() );
|
|
game.World.Env.SetEdgeLevel( reader.ReadInt16() );
|
|
if( net.cpeData.envMapVer == 1 ) return;
|
|
|
|
// Version 2
|
|
game.World.Env.SetCloudsLevel( reader.ReadInt16() );
|
|
short maxViewDist = reader.ReadInt16();
|
|
game.MaxViewDistance = maxViewDist <= 0 ? 32768 : maxViewDist;
|
|
game.SetViewDistance( game.UserViewDistance, false );
|
|
}
|
|
|
|
void HandleEnvWeatherType() {
|
|
game.World.Env.SetWeather( (Weather)reader.ReadUInt8() );
|
|
}
|
|
|
|
void HandleHackControl() {
|
|
LocalPlayer p = game.LocalPlayer;
|
|
p.Hacks.CanFly = reader.ReadUInt8() != 0;
|
|
p.Hacks.CanNoclip = reader.ReadUInt8() != 0;
|
|
p.Hacks.CanSpeed = reader.ReadUInt8() != 0;
|
|
p.Hacks.CanRespawn = reader.ReadUInt8() != 0;
|
|
p.Hacks.CanUseThirdPersonCamera = reader.ReadUInt8() != 0;
|
|
p.CheckHacksConsistency();
|
|
|
|
float jumpHeight = reader.ReadInt16() / 32f;
|
|
if( jumpHeight < 0 )
|
|
p.physics.jumpVel = p.Hacks.CanJumpHigher ? p.physics.userJumpVel : 0.42f;
|
|
else
|
|
p.physics.CalculateJumpVelocity( false, jumpHeight );
|
|
p.physics.serverJumpVel = p.physics.jumpVel;
|
|
game.Events.RaiseHackPermissionsChanged();
|
|
}
|
|
|
|
void HandleExtAddEntity2() {
|
|
byte id = reader.ReadUInt8();
|
|
string displayName = reader.ReadAsciiString();
|
|
string skinName = reader.ReadAsciiString();
|
|
net.CheckName( id, ref displayName, ref skinName );
|
|
net.AddEntity( id, displayName, skinName, true );
|
|
}
|
|
|
|
const int bulkCount = 256;
|
|
unsafe void HandleBulkBlockUpdate() {
|
|
int count = reader.ReadUInt8() + 1;
|
|
if( game.World.IsNotLoaded ) {
|
|
#if DEBUG_BLOCKS
|
|
Utils.LogDebug( "Server tried to update a block while still sending us the map!" );
|
|
#endif
|
|
reader.Skip( bulkCount * (sizeof(int) + 1) );
|
|
return;
|
|
}
|
|
int* indices = stackalloc int[bulkCount];
|
|
for( int i = 0; i < count; i++ )
|
|
indices[i] = reader.ReadInt32();
|
|
reader.Skip( (bulkCount - count) * sizeof(int) );
|
|
|
|
for( int i = 0; i < count; i++ ) {
|
|
byte block = reader.ReadUInt8();
|
|
Vector3I coords = game.World.GetCoords( indices[i] );
|
|
|
|
if( coords.X < 0 ) {
|
|
#if DEBUG_BLOCKS
|
|
Utils.LogDebug( "Server tried to update a block at an invalid position!" );
|
|
#endif
|
|
continue;
|
|
}
|
|
game.UpdateBlock( coords.X, coords.Y, coords.Z, block );
|
|
}
|
|
reader.Skip( bulkCount - count );
|
|
}
|
|
|
|
void HandleSetTextColor() {
|
|
FastColour col = new FastColour( reader.ReadUInt8(), reader.ReadUInt8(),
|
|
reader.ReadUInt8(), reader.ReadUInt8() );
|
|
byte code = reader.ReadUInt8();
|
|
|
|
if( code <= ' ' || code > '~' ) return; // Control chars, space, extended chars cannot be used
|
|
if( (code >= '0' && code <= '9') || (code >= 'a' && code <= 'f')
|
|
|| (code >= 'A' && code <= 'F') ) return; // Standard chars cannot be used.
|
|
if( code == '%' || code == '&' ) return; // colour code signifiers cannot be used
|
|
|
|
game.Drawer2D.Colours[code] = col;
|
|
game.Events.RaiseColourCodeChanged( (char)code );
|
|
}
|
|
|
|
void HandleSetMapEnvUrl() {
|
|
string url = reader.ReadAsciiString();
|
|
if( !game.AllowServerTextures ) return;
|
|
|
|
if( url == "" ) {
|
|
TexturePack.ExtractDefault( game );
|
|
} else if( Utils.IsUrlPrefix( url, 0 ) ) {
|
|
net.RetrieveTexturePack( url );
|
|
}
|
|
Utils.LogDebug( "Image url: " + url );
|
|
}
|
|
|
|
void HandleSetMapEnvProperty() {
|
|
byte type = reader.ReadUInt8();
|
|
int value = reader.ReadInt32();
|
|
WorldEnv env = game.World.Env;
|
|
Utils.Clamp( ref value, short.MinValue, short.MaxValue );
|
|
|
|
switch( type ) {
|
|
case 0:
|
|
Utils.Clamp( ref value, byte.MinValue, byte.MaxValue );
|
|
env.SetSidesBlock( (byte)value ); break;
|
|
case 1:
|
|
Utils.Clamp( ref value, byte.MinValue, byte.MaxValue );
|
|
env.SetEdgeBlock( (byte)value ); break;
|
|
case 2:
|
|
env.SetEdgeLevel( value ); break;
|
|
case 3:
|
|
env.SetCloudsLevel( value ); break;
|
|
case 4:
|
|
game.MaxViewDistance = value <= 0 ? 32768 : value;
|
|
game.SetViewDistance( game.UserViewDistance, false ); break;
|
|
case 5:
|
|
env.SetCloudsSpeed( value / 256f ); break;
|
|
case 6:
|
|
env.SetWeatherSpeed( value / 256f ); break;
|
|
case 7:
|
|
Utils.Clamp( ref value, byte.MinValue, byte.MaxValue );
|
|
Console.WriteLine( value );
|
|
env.SetWeatherFade( value / 128f ); break;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Write
|
|
|
|
internal void SendPlayerClick( MouseButton button, bool buttonDown,
|
|
byte targetId, PickedPos pos ) {
|
|
Player p = game.LocalPlayer;
|
|
writer.WriteUInt8( (byte)Opcode.CpePlayerClick );
|
|
writer.WriteUInt8( (byte)button );
|
|
writer.WriteUInt8( buttonDown ? (byte)0 : (byte)1 );
|
|
writer.WriteInt16( (short)Utils.DegreesToPacked( p.HeadYawDegrees, 65536 ) );
|
|
writer.WriteInt16( (short)Utils.DegreesToPacked( p.PitchDegrees, 65536 ) );
|
|
|
|
writer.WriteUInt8( targetId );
|
|
writer.WriteInt16( (short)pos.BlockPos.X );
|
|
writer.WriteInt16( (short)pos.BlockPos.Y );
|
|
writer.WriteInt16( (short)pos.BlockPos.Z );
|
|
writer.WriteUInt8( (byte)pos.BlockFace );
|
|
net.SendPacket();
|
|
}
|
|
|
|
internal void SendExtInfo( string appName, int extensionsCount ) {
|
|
writer.WriteUInt8( (byte)Opcode.CpeExtInfo );
|
|
writer.WriteString( appName );
|
|
writer.WriteInt16( (short)extensionsCount );
|
|
net.SendPacket();
|
|
}
|
|
|
|
internal void SendExtEntry( string extensionName, int extensionVersion ) {
|
|
writer.WriteUInt8( (byte)Opcode.CpeExtEntry );
|
|
writer.WriteString( extensionName );
|
|
writer.WriteInt32( extensionVersion );
|
|
net.SendPacket();
|
|
}
|
|
|
|
internal void SendCustomBlockSupportLevel( byte version ) {
|
|
writer.WriteUInt8( (byte)Opcode.CpeCustomBlockSupportLevel );
|
|
writer.WriteUInt8( version );
|
|
net.SendPacket();
|
|
}
|
|
|
|
void SendCpeExtInfoReply() {
|
|
if( net.cpeData.ServerExtensionsCount != 0 ) return;
|
|
string[] clientExts = CPESupport.ClientExtensions;
|
|
int count = clientExts.Length;
|
|
if( !game.AllowCustomBlocks ) count -= 2;
|
|
|
|
SendExtInfo( Program.AppName, count );
|
|
for( int i = 0; i < clientExts.Length; i++ ) {
|
|
string name = clientExts[i];
|
|
int ver = 1;
|
|
if( name == "ExtPlayerList" ) ver = 2;
|
|
if( name == "EnvMapAppearance" ) ver = net.cpeData.envMapVer;
|
|
if( name == "BlockDefinitionsExt" ) ver = net.cpeData.blockDefsExtVer;
|
|
|
|
if( !game.AllowCustomBlocks && name.StartsWith( "BlockDefinitions" ) )
|
|
continue;
|
|
SendExtEntry( name, ver );
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|