diff --git a/ClassicalSharp/Audio/AudioPlayer.Sounds.cs b/ClassicalSharp/Audio/AudioPlayer.Sounds.cs index 56263af3f..3b04601f7 100644 --- a/ClassicalSharp/Audio/AudioPlayer.Sounds.cs +++ b/ClassicalSharp/Audio/AudioPlayer.Sounds.cs @@ -46,7 +46,7 @@ namespace ClassicalSharp.Audio { void PlaySound( SoundType type, Soundboard board ) { if( type == SoundType.None || monoOutputs == null ) return; - Sound snd = board.PlayRandomSound( type ); + Sound snd = board.PickRandomSound( type ); snd.Metadata = board == digBoard ? (byte)1 : (byte)2; chunk.Channels = snd.Channels; chunk.Frequency = snd.SampleRate; @@ -72,14 +72,28 @@ namespace ClassicalSharp.Audio { firstSoundOut = output; outputs[i] = output; } + if( !output.DoneRawAsync() ) continue; - if( output.DoneRawAsync() ) { + try { output.PlayRawAsync( chunk ); - return; + } catch( InvalidOperationException ex ) { + HandleSoundError( ex ); } + return; } } + void HandleSoundError( InvalidOperationException ex ) { + ErrorHandler.LogError( "AudioPlayer.PlayCurrentSound()", ex ); + if( ex.Message == "No audio devices found" ) + game.Chat.Add( "&cNo audio devices found, disabling sounds." ); + else + game.Chat.Add( "&cAn error occured when trying to play sounds, disabling sounds." ); + + SetSound( false ); + game.UseSound = false; + } + void DisposeSound() { DisposeOutputs( ref monoOutputs ); DisposeOutputs( ref stereoOutputs ); @@ -108,6 +122,6 @@ namespace ClassicalSharp.Audio { outputs[i].Dispose(); } outputs = null; - } + } } } \ No newline at end of file diff --git a/ClassicalSharp/Audio/AudioPlayer.cs b/ClassicalSharp/Audio/AudioPlayer.cs index 4f2a7ef64..031fc5b5d 100644 --- a/ClassicalSharp/Audio/AudioPlayer.cs +++ b/ClassicalSharp/Audio/AudioPlayer.cs @@ -12,8 +12,10 @@ namespace ClassicalSharp.Audio { IAudioOutput[] monoOutputs, stereoOutputs; string[] musicFiles; Thread musicThread; + Game game; public AudioPlayer( Game game ) { + this.game = game; game.UseMusic = Options.GetBool( OptionsKey.UseMusic, false ); SetMusic( game.UseMusic ); game.UseSound = Options.GetBool( OptionsKey.UseSound, false ); @@ -45,7 +47,12 @@ namespace ClassicalSharp.Audio { using( FileStream fs = File.OpenRead( path ) ) { OggContainer container = new OggContainer( fs ); - musicOut.PlayStreaming( container ); + try { + musicOut.PlayStreaming( container ); + } catch( InvalidOperationException ex) { + HandleMusicError( ex ); + return; + } } if( disposingMusic ) break; @@ -54,6 +61,17 @@ namespace ClassicalSharp.Audio { } } + void HandleMusicError( InvalidOperationException ex ) { + ErrorHandler.LogError( "AudioPlayer.DoMusicThread()", ex ); + if( ex.Message == "No audio devices found" ) + game.Chat.Add( "&cNo audio devices found, disabling music." ); + else + game.Chat.Add( "&cAn error occured when trying to play music, disabling music." ); + + SetMusic( false ); + game.UseMusic = false; + } + bool disposingMusic; public void Dispose() { DisposeMusic(); diff --git a/ClassicalSharp/Audio/Soundboard.cs b/ClassicalSharp/Audio/Soundboard.cs index 0731f5f96..0dad729aa 100644 --- a/ClassicalSharp/Audio/Soundboard.cs +++ b/ClassicalSharp/Audio/Soundboard.cs @@ -29,7 +29,7 @@ namespace ClassicalSharp.Audio { GetGroups(); } - public Sound PlayRandomSound( SoundType type ) { + public Sound PickRandomSound( SoundType type ) { if( type == SoundType.None ) return null; string name = soundNames[(int)type]; diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index adbbdae1f..ae60df91a 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -171,6 +171,7 @@ + diff --git a/ClassicalSharp/Model/CustomModel.cs b/ClassicalSharp/Model/CustomModel.cs index 0ad23c120..04cc97180 100644 --- a/ClassicalSharp/Model/CustomModel.cs +++ b/ClassicalSharp/Model/CustomModel.cs @@ -29,10 +29,14 @@ namespace ClassicalSharp.Model { int texId = p.PlayerTextureId <= 0 ? cache.HumanoidTexId : p.PlayerTextureId; } + internal void ReadSetupPacket( NetReader reader ) { + + } + internal void ReadMetadataPacket( NetReader reader ) { - collisonSize = ReadVector( reader ); - pickingBounds.Min = ReadVector( reader ); - pickingBounds.Max = ReadVector( reader ); + collisonSize = ReadS16Vec3( reader ); + pickingBounds.Min = ReadS16Vec3( reader ); + pickingBounds.Max = ReadS16Vec3( reader ); nameYOffset = reader.ReadInt16() / 256f; eyeY = reader.ReadInt16() / 256f; bobbing = reader.ReadUInt8() != 0; @@ -41,8 +45,8 @@ namespace ClassicalSharp.Model { internal void ReadDefinePartPacket( NetReader reader ) { ushort partId = reader.ReadUInt16(); byte type = reader.ReadUInt8(); - Vector3 min = ReadVector( reader ); - Vector3 max = ReadVector( reader ); + Vector3 min = ReadS16Vec3( reader ); + Vector3 max = ReadS16Vec3( reader ); } internal void ReadRotationPacket( NetReader reader ) { @@ -54,7 +58,7 @@ namespace ClassicalSharp.Model { } CustomModelPart[] parts; - Vector3 ReadVector( NetReader reader ) { + Vector3 ReadS16Vec3( NetReader reader ) { return new Vector3( reader.ReadInt16() / 256f, reader.ReadInt16() / 256f, reader.ReadInt16() / 256f ); } diff --git a/ClassicalSharp/Model/ModelCache.cs b/ClassicalSharp/Model/ModelCache.cs index 89d24623f..2a7e6e371 100644 --- a/ClassicalSharp/Model/ModelCache.cs +++ b/ClassicalSharp/Model/ModelCache.cs @@ -13,6 +13,7 @@ namespace ClassicalSharp.Model { this.game = window; api = game.Graphics; } + public CustomModel[] CustomModels = new CustomModel[256]; public void InitCache() { vertices = new VertexPos3fTex2fCol4b[24 * 12]; diff --git a/ClassicalSharp/Network/NetworkProcessor.CPE.cs b/ClassicalSharp/Network/NetworkProcessor.CPE.cs index 2be1c2e64..a884a74e6 100644 --- a/ClassicalSharp/Network/NetworkProcessor.CPE.cs +++ b/ClassicalSharp/Network/NetworkProcessor.CPE.cs @@ -358,108 +358,6 @@ namespace ClassicalSharp { AddEntity( entityId, displayName, skinName, true ); } - void HandleCpeDefineBlock() { - if( !game.AllowCustomBlocks ) { - SkipPacketData( PacketId.CpeDefineBlock ); return; - } - byte id = HandleCpeDefineBlockCommonStart( false ); - BlockInfo info = game.BlockInfo; - byte shape = reader.ReadUInt8(); - if( shape == 0 ) { - info.IsSprite[id] = true; - info.IsOpaque[id] = false; - info.IsTransparent[id] = true; - } else if( shape <= 16 ) { - info.MaxBB[id].Y = shape / 16f; - } - - HandleCpeDefineBlockCommonEnd( id ); - // Update sprite BoundingBox if necessary - if( info.IsSprite[id] ) { - using( FastBitmap dst = new FastBitmap( game.TerrainAtlas.AtlasBitmap, true, true ) ) - info.RecalculateBB( id, dst ); - } - info.DefinedCustomBlocks[id >> 5] |= (1u << (id & 0x1F)); - } - - void HandleCpeRemoveBlockDefinition() { - if( !game.AllowCustomBlocks ) { - SkipPacketData( PacketId.CpeRemoveBlockDefinition ); return; - } - game.BlockInfo.ResetBlockInfo( reader.ReadUInt8(), true ); - game.BlockInfo.InitLightOffsets(); - game.Events.RaiseBlockDefinitionChanged(); - } - - void HandleCpeDefineBlockExt() { - if( !game.AllowCustomBlocks ) { - SkipPacketData( PacketId.CpeDefineBlockExt ); return; - } - byte id = HandleCpeDefineBlockCommonStart( blockDefinitionsExtVer >= 2 ); - BlockInfo info = game.BlockInfo; - Vector3 min, max; - - min.X = reader.ReadUInt8() / 16f; Utils.Clamp( ref min.X, 0, 15/16f ); - min.Y = reader.ReadUInt8() / 16f; Utils.Clamp( ref min.Y, 0, 15/16f ); - min.Z = reader.ReadUInt8() / 16f; Utils.Clamp( ref min.Z, 0, 15/16f ); - max.X = reader.ReadUInt8() / 16f; Utils.Clamp( ref max.X, 1/16f, 1 ); - max.Y = reader.ReadUInt8() / 16f; Utils.Clamp( ref max.Y, 1/16f, 1 ); - max.Z = reader.ReadUInt8() / 16f; Utils.Clamp( ref max.Z, 1/16f, 1 ); - - info.MinBB[id] = min; - info.MaxBB[id] = max; - HandleCpeDefineBlockCommonEnd( id ); - info.DefinedCustomBlocks[id >> 5] |= (1u << (id & 0x1F)); - } - - byte HandleCpeDefineBlockCommonStart( bool uniqueSideTexs ) { - byte block = reader.ReadUInt8(); - BlockInfo info = game.BlockInfo; - info.ResetBlockInfo( block, false ); - - info.Name[block] = reader.ReadAsciiString(); - info.CollideType[block] = (BlockCollideType)reader.ReadUInt8(); - if( info.CollideType[block] != BlockCollideType.Solid ) { - info.IsTransparent[block] = true; - info.IsOpaque[block] = false; - } - - info.SpeedMultiplier[block] = (float)Math.Pow( 2, (reader.ReadUInt8() - 128) / 64f ); - info.SetTex( reader.ReadUInt8(), TileSide.Top, (Block)block ); - if( uniqueSideTexs ) { - info.SetTex( reader.ReadUInt8(), TileSide.Left, (Block)block ); - info.SetTex( reader.ReadUInt8(), TileSide.Right, (Block)block ); - info.SetTex( reader.ReadUInt8(), TileSide.Front, (Block)block ); - info.SetTex( reader.ReadUInt8(), TileSide.Back, (Block)block ); - } else { - info.SetSide( reader.ReadUInt8(), (Block)block ); - } - info.SetTex( reader.ReadUInt8(), TileSide.Bottom, (Block)block ); - - info.BlocksLight[block] = reader.ReadUInt8() == 0; - byte sound = reader.ReadUInt8(); - if( sound < breakSnds.Length ) { - info.StepSounds[block] = stepSnds[sound]; - info.DigSounds[block] = breakSnds[sound]; - } - info.FullBright[block] = reader.ReadUInt8() != 0; - return block; - } - - void HandleCpeDefineBlockCommonEnd( byte block ) { - BlockInfo info = game.BlockInfo; - byte blockDraw = reader.ReadUInt8(); - SetBlockDraw( info, block, blockDraw ); - - byte fogDensity = reader.ReadUInt8(); - info.FogDensity[block] = fogDensity == 0 ? 0 : (fogDensity + 1) / 128f; - info.FogColour[block] = new FastColour( - reader.ReadUInt8(), reader.ReadUInt8(), reader.ReadUInt8() ); - info.SetupCullingCache( block ); - info.InitLightOffsets(); - game.Events.RaiseBlockDefinitionChanged(); - } - const int bulkCount = 256; unsafe void HandleBulkBlockUpdate() { int count = reader.ReadUInt8() + 1; @@ -498,59 +396,10 @@ namespace ClassicalSharp { 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.RaiseColourCodesChanged(); } - - void HandleDefineModel() { - int start = reader.index - 1; - byte modelId = reader.ReadUInt8(); - switch( reader.ReadUInt8() ) { - case 0: // setup - break; - case 1: // metadata - break; - case 2: // define part - break; - case 3: // rotation - break; - } - int read = reader.index - start; - // TODO: skip remaining data - } - - internal static SoundType[] stepSnds, breakSnds; - static NetworkProcessor() { - stepSnds = new SoundType[10]; - breakSnds = new SoundType[10]; - stepSnds[0] = SoundType.None; breakSnds[0] = SoundType.None; - stepSnds[1] = SoundType.Wood; breakSnds[1] = SoundType.Wood; - stepSnds[2] = SoundType.Gravel; breakSnds[2] = SoundType.Gravel; - stepSnds[3] = SoundType.Grass; breakSnds[3] = SoundType.Grass; - stepSnds[4] = SoundType.Stone; breakSnds[4] = SoundType.Stone; - // TODO: metal sound type, just use stone for now. - stepSnds[5] = SoundType.Stone; breakSnds[5] = SoundType.Stone; - stepSnds[6] = SoundType.Stone; breakSnds[6] = SoundType.Glass; - stepSnds[7] = SoundType.Cloth; breakSnds[7] = SoundType.Cloth; - stepSnds[8] = SoundType.Sand; breakSnds[8] = SoundType.Sand; - stepSnds[9] = SoundType.Snow; breakSnds[9] = SoundType.Snow; - } - - internal static void SetBlockDraw( BlockInfo info, byte block, byte blockDraw ) { - if( blockDraw == 1 ) { - info.IsTransparent[block] = true; - } else if( blockDraw == 2 ) { - info.IsTransparent[block] = true; - info.CullWithNeighbours[block] = false; - } else if( blockDraw == 3 ) { - info.IsTranslucent[block] = true; - } else if( blockDraw == 4 ) { - info.IsTransparent[block] = true; - info.IsAir[block] = true; - } - if( info.IsOpaque[block] ) - info.IsOpaque[block] = blockDraw == 0; - } } #endregion } \ No newline at end of file diff --git a/ClassicalSharp/Network/NetworkProcessor.CPECustom.cs b/ClassicalSharp/Network/NetworkProcessor.CPECustom.cs new file mode 100644 index 000000000..1d753e561 --- /dev/null +++ b/ClassicalSharp/Network/NetworkProcessor.CPECustom.cs @@ -0,0 +1,172 @@ +using System; +using ClassicalSharp.Model; +using OpenTK; + +namespace ClassicalSharp { + + public partial class NetworkProcessor : INetworkProcessor { + + void HandleCpeDefineBlock() { + if( !game.AllowCustomBlocks ) { + SkipPacketData( PacketId.CpeDefineBlock ); return; + } + byte id = HandleCpeDefineBlockCommonStart( false ); + BlockInfo info = game.BlockInfo; + byte shape = reader.ReadUInt8(); + if( shape == 0 ) { + info.IsSprite[id] = true; + info.IsOpaque[id] = false; + info.IsTransparent[id] = true; + } else if( shape <= 16 ) { + info.MaxBB[id].Y = shape / 16f; + } + + HandleCpeDefineBlockCommonEnd( id ); + // Update sprite BoundingBox if necessary + if( info.IsSprite[id] ) { + using( FastBitmap dst = new FastBitmap( game.TerrainAtlas.AtlasBitmap, true, true ) ) + info.RecalculateBB( id, dst ); + } + info.DefinedCustomBlocks[id >> 5] |= (1u << (id & 0x1F)); + } + + void HandleCpeRemoveBlockDefinition() { + if( !game.AllowCustomBlocks ) { + SkipPacketData( PacketId.CpeRemoveBlockDefinition ); return; + } + game.BlockInfo.ResetBlockInfo( reader.ReadUInt8(), true ); + game.BlockInfo.InitLightOffsets(); + game.Events.RaiseBlockDefinitionChanged(); + } + + void HandleCpeDefineBlockExt() { + if( !game.AllowCustomBlocks ) { + SkipPacketData( PacketId.CpeDefineBlockExt ); return; + } + byte id = HandleCpeDefineBlockCommonStart( blockDefinitionsExtVer >= 2 ); + BlockInfo info = game.BlockInfo; + Vector3 min, max; + + min.X = reader.ReadUInt8() / 16f; Utils.Clamp( ref min.X, 0, 15/16f ); + min.Y = reader.ReadUInt8() / 16f; Utils.Clamp( ref min.Y, 0, 15/16f ); + min.Z = reader.ReadUInt8() / 16f; Utils.Clamp( ref min.Z, 0, 15/16f ); + max.X = reader.ReadUInt8() / 16f; Utils.Clamp( ref max.X, 1/16f, 1 ); + max.Y = reader.ReadUInt8() / 16f; Utils.Clamp( ref max.Y, 1/16f, 1 ); + max.Z = reader.ReadUInt8() / 16f; Utils.Clamp( ref max.Z, 1/16f, 1 ); + + info.MinBB[id] = min; + info.MaxBB[id] = max; + HandleCpeDefineBlockCommonEnd( id ); + info.DefinedCustomBlocks[id >> 5] |= (1u << (id & 0x1F)); + } + + byte HandleCpeDefineBlockCommonStart( bool uniqueSideTexs ) { + byte block = reader.ReadUInt8(); + BlockInfo info = game.BlockInfo; + info.ResetBlockInfo( block, false ); + + info.Name[block] = reader.ReadAsciiString(); + info.CollideType[block] = (BlockCollideType)reader.ReadUInt8(); + if( info.CollideType[block] != BlockCollideType.Solid ) { + info.IsTransparent[block] = true; + info.IsOpaque[block] = false; + } + + info.SpeedMultiplier[block] = (float)Math.Pow( 2, (reader.ReadUInt8() - 128) / 64f ); + info.SetTex( reader.ReadUInt8(), TileSide.Top, (Block)block ); + if( uniqueSideTexs ) { + info.SetTex( reader.ReadUInt8(), TileSide.Left, (Block)block ); + info.SetTex( reader.ReadUInt8(), TileSide.Right, (Block)block ); + info.SetTex( reader.ReadUInt8(), TileSide.Front, (Block)block ); + info.SetTex( reader.ReadUInt8(), TileSide.Back, (Block)block ); + } else { + info.SetSide( reader.ReadUInt8(), (Block)block ); + } + info.SetTex( reader.ReadUInt8(), TileSide.Bottom, (Block)block ); + + info.BlocksLight[block] = reader.ReadUInt8() == 0; + byte sound = reader.ReadUInt8(); + if( sound < breakSnds.Length ) { + info.StepSounds[block] = stepSnds[sound]; + info.DigSounds[block] = breakSnds[sound]; + } + info.FullBright[block] = reader.ReadUInt8() != 0; + return block; + } + + void HandleCpeDefineBlockCommonEnd( byte block ) { + BlockInfo info = game.BlockInfo; + byte blockDraw = reader.ReadUInt8(); + SetBlockDraw( info, block, blockDraw ); + + byte fogDensity = reader.ReadUInt8(); + info.FogDensity[block] = fogDensity == 0 ? 0 : (fogDensity + 1) / 128f; + info.FogColour[block] = new FastColour( + reader.ReadUInt8(), reader.ReadUInt8(), reader.ReadUInt8() ); + info.SetupCullingCache( block ); + info.InitLightOffsets(); + game.Events.RaiseBlockDefinitionChanged(); + } + + void HandleDefineModel() { + int start = reader.index - 1; + byte id = reader.ReadUInt8(); + CustomModel model = null; + + switch( reader.ReadUInt8() ) { + case 0: + model = new CustomModel( game ); + model.ReadSetupPacket( reader ); + game.ModelCache.CustomModels[id] = model; + break; + case 1: + model = game.ModelCache.CustomModels[id]; + if( model != null ) model.ReadMetadataPacket( reader ); + break; + case 2: + model = game.ModelCache.CustomModels[id]; + if( model != null ) model.ReadDefinePartPacket( reader ); + break; + case 3: + model = game.ModelCache.CustomModels[id]; + if( model != null ) model.ReadRotationPacket( reader ); + break; + } + int total = packetSizes[(byte)PacketId.CpeDefineModel]; + reader.Skip( total - (reader.index - start) ); + } + + internal static SoundType[] stepSnds, breakSnds; + static NetworkProcessor() { + stepSnds = new SoundType[10]; + breakSnds = new SoundType[10]; + stepSnds[0] = SoundType.None; breakSnds[0] = SoundType.None; + stepSnds[1] = SoundType.Wood; breakSnds[1] = SoundType.Wood; + stepSnds[2] = SoundType.Gravel; breakSnds[2] = SoundType.Gravel; + stepSnds[3] = SoundType.Grass; breakSnds[3] = SoundType.Grass; + stepSnds[4] = SoundType.Stone; breakSnds[4] = SoundType.Stone; + // TODO: metal sound type, just use stone for now. + stepSnds[5] = SoundType.Stone; breakSnds[5] = SoundType.Stone; + stepSnds[6] = SoundType.Stone; breakSnds[6] = SoundType.Glass; + stepSnds[7] = SoundType.Cloth; breakSnds[7] = SoundType.Cloth; + stepSnds[8] = SoundType.Sand; breakSnds[8] = SoundType.Sand; + stepSnds[9] = SoundType.Snow; breakSnds[9] = SoundType.Snow; + } + + internal static void SetBlockDraw( BlockInfo info, byte block, byte blockDraw ) { + if( blockDraw == 1 ) { + info.IsTransparent[block] = true; + } else if( blockDraw == 2 ) { + info.IsTransparent[block] = true; + info.CullWithNeighbours[block] = false; + } else if( blockDraw == 3 ) { + info.IsTranslucent[block] = true; + } else if( blockDraw == 4 ) { + info.IsTransparent[block] = true; + info.IsAir[block] = true; + } + if( info.IsOpaque[block] ) + info.IsOpaque[block] = blockDraw == 0; + } + } +} \ No newline at end of file diff --git a/ClassicalSharp/Network/Utils/NetReader.cs b/ClassicalSharp/Network/Utils/NetReader.cs index bee777994..aeb08ec67 100644 --- a/ClassicalSharp/Network/Utils/NetReader.cs +++ b/ClassicalSharp/Network/Utils/NetReader.cs @@ -73,20 +73,22 @@ namespace ClassicalSharp { public string ReadCp437String() { int length = GetString( false, 64 ); - index += 64; return new String( characters, 0, length ); } public string ReadAsciiString() { int length = GetString( true, 64 ); - index += 64; + return new String( characters, 0, length ); + } + + public string ReadAsciiString( int maxLength ) { + int length = GetString( true, maxLength ); return new String( characters, 0, length ); } internal string ReadChatString( ref byte messageType, bool useMessageTypes ) { if( !useMessageTypes ) messageType = (byte)MessageType.Normal; - int length = GetString( false, 64 ); - index += 64; + int length = GetString( false, 64 ); int offset = 0; if( length >= womDetail.Length && IsWomDetailString() ) { @@ -107,9 +109,10 @@ namespace ClassicalSharp { return true; } - int GetString( bool ascii, int bufferSize ) { + int GetString( bool ascii, int maxLength ) { int length = 0; - for( int i = bufferSize - 1; i >= 0; i-- ) { + + for( int i = maxLength - 1; i >= 0; i-- ) { byte code = buffer[index + i]; if( length == 0 && !( code == 0 || code == 0x20 ) ) length = i + 1; @@ -128,6 +131,7 @@ namespace ClassicalSharp { characters[i] = Utils.ExtendedCharReplacements[code - 0x7F]; } } + index += maxLength; return length; } } diff --git a/ClassicalSharp/SharpWave.dll b/ClassicalSharp/SharpWave.dll index c567e2cee..eacd96ff1 100644 Binary files a/ClassicalSharp/SharpWave.dll and b/ClassicalSharp/SharpWave.dll differ