diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index bda21043f..90cbee10e 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -71,6 +71,9 @@ + + SharpWave.dll + @@ -243,6 +246,9 @@ PreserveNewest + + PreserveNewest + diff --git a/ClassicalSharp/Particles/CollidableParticle.cs b/ClassicalSharp/Particles/CollidableParticle.cs new file mode 100644 index 000000000..a74a04b2e --- /dev/null +++ b/ClassicalSharp/Particles/CollidableParticle.cs @@ -0,0 +1,66 @@ +using System; +using ClassicalSharp.Entities; +using OpenTK; + +namespace ClassicalSharp.Particles { + + public abstract class CollidableParticle : Particle { + + const float gravity = 3.4f; + //const float gravity = 0.7f; // TODO: temp debug + + public CollidableParticle( Game game, Vector3 pos, Vector3 velocity, double lifetime ) + : base( game, pos, velocity, lifetime ) { + } + + public override bool Tick( double delta ) { + lastPos = Position = nextPos; + byte curBlock = game.World.SafeGetBlock( (int)Position.X, (int)Position.Y, (int)Position.Z ); + if( !CanPassThrough( curBlock ) ) return true; + + Velocity.Y -= gravity * (float)delta; + int startY = (int)Math.Floor( Position.Y ); + Position += Velocity * (float)delta * 3; + int endY = (int)Math.Floor( Position.Y ); + Utils.Clamp( ref Position.X, 0, game.World.Width - 0.01f ); + Utils.Clamp( ref Position.Z, 0, game.World.Length - 0.01f ); + + if( Velocity.Y > 0 ) { + for( int y = startY; y <= endY && TestY( y, false ); y++ ); + } else { + for( int y = startY; y >= endY && TestY( y, true ); y-- ); + } + nextPos = Position; + Position = lastPos; + return base.Tick( delta ); + } + + bool TestY( int y, bool topFace ) { + if( y < 0 ) { + Position.Y = nextPos.Y = lastPos.Y = 0 + Entity.Adjustment; + Velocity = Vector3.Zero; + return false; + } + + byte block = game.World.SafeGetBlock( (int)Position.X, y, (int)Position.Z ); + if( CanPassThrough( block ) ) return true; + + float collideY = y; + if( topFace ) + collideY += game.BlockInfo.Height[block]; + + bool collide = topFace ? (Position.Y < collideY) : (Position.Y > collideY ); + if( collide ) { + float adjust = topFace ? Entity.Adjustment : -Entity.Adjustment; + Position.Y = nextPos.Y = lastPos.Y = collideY + adjust; + Velocity = Vector3.Zero; + return false; + } + return true; + } + + bool CanPassThrough( byte block ) { + return block == 0 || game.BlockInfo.IsSprite[block] || game.BlockInfo.IsLiquid[block]; + } + } +} diff --git a/ClassicalSharp/Particles/Particle.cs b/ClassicalSharp/Particles/Particle.cs new file mode 100644 index 000000000..6ff57d9f9 --- /dev/null +++ b/ClassicalSharp/Particles/Particle.cs @@ -0,0 +1,28 @@ +using System; +using OpenTK; + +namespace ClassicalSharp.Particles { + + public abstract class Particle { + + public Vector3 Position; + public Vector3 Velocity; + public float Lifetime; + protected Game game; + protected Vector3 lastPos, nextPos; + + public abstract void Render( double delta, float t, VertexPos3fTex2fCol4b[] vertices, ref int index ); + + public Particle( Game game, Vector3 pos, Vector3 velocity, double lifetime ) { + this.game = game; + Position = lastPos = nextPos = pos; + Velocity = velocity; + Lifetime = (float)lifetime; + } + + public virtual bool Tick( double delta ) { + Lifetime -= (float)delta; + return Lifetime < 0; + } + } +} \ No newline at end of file diff --git a/ClassicalSharp/Particles/ParticleManager.cs b/ClassicalSharp/Particles/ParticleManager.cs new file mode 100644 index 000000000..115206830 --- /dev/null +++ b/ClassicalSharp/Particles/ParticleManager.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using ClassicalSharp.GraphicsAPI; +using OpenTK; + +namespace ClassicalSharp.Particles { + + public class ParticleManager : IDisposable { + + public int ParticlesTexId; + List terrainParticles = new List(); + List rainParticles = new List(); + Game game; + Random rnd; + int vb; + + public ParticleManager( Game game ) { + this.game = game; + rnd = new Random(); + vb = game.Graphics.CreateDynamicVb( VertexFormat.Pos3fTex2fCol4b, 1000 ); + } + + VertexPos3fTex2fCol4b[] vertices = new VertexPos3fTex2fCol4b[0]; + public void Render( double delta, float t ) { + if( terrainParticles.Count == 0 && rainParticles.Count == 0 ) return; + IGraphicsApi graphics = game.Graphics; + graphics.Texturing = true; + graphics.AlphaTest = true; + graphics.SetBatchFormat( VertexFormat.Pos3fTex2fCol4b ); + + int count = RenderParticles( terrainParticles, delta, t ); + if( count > 0 ) { + graphics.BindTexture( game.TerrainAtlas.TexId ); + graphics.UpdateDynamicIndexedVb( DrawMode.Triangles, vb, vertices, count, count * 6 / 4 ); + } + count = RenderParticles( rainParticles, delta, t ); + if( count > 0 ) { + graphics.BindTexture( ParticlesTexId ); + graphics.UpdateDynamicIndexedVb( DrawMode.Triangles, vb, vertices, count, count * 6 / 4 ); + } + + graphics.AlphaTest = false; + graphics.Texturing = false; + } + + int RenderParticles( List particles, double delta, float t ) { + int count = particles.Count * 4; + if( count > vertices.Length ) + vertices = new VertexPos3fTex2fCol4b[count]; + + int index = 0; + for( int i = 0; i < particles.Count; i++ ) + particles[i].Render( delta, t, vertices, ref index ); + return Math.Min( count, 1000 ); + } + + public void Tick( double delta ) { + TickParticles( terrainParticles, delta ); + TickParticles( rainParticles, delta ); + } + + void TickParticles( List particles, double delta ) { + for( int i = 0; i < particles.Count; i++ ) { + Particle particle = particles[i]; + if( particle.Tick( delta ) ) { + particles.RemoveAt( i ); + i--; + } + } + } + + public void Dispose() { + game.Graphics.DeleteDynamicVb( vb ); + game.Graphics.DeleteTexture( ref ParticlesTexId ); + } + + public void BreakBlockEffect( Vector3I position, byte block ) { + Vector3 startPos = new Vector3( position.X, position.Y, position.Z ); + int texLoc = game.BlockInfo.GetTextureLoc( block, TileSide.Left ); + TextureRec rec = game.TerrainAtlas.GetTexRec( texLoc ); + + const float invSize = TerrainAtlas2D.invElementSize; + const int cellsCount = (int)((1/4f) / invSize); + const float elemSize = invSize / 4f; + float blockHeight = game.BlockInfo.Height[block]; + + for( int i = 0; i < 25; i++ ) { + double velX = rnd.NextDouble() * 0.8 - 0.4; // [-0.4, 0.4] + double velZ = rnd.NextDouble() * 0.8 - 0.4; + double velY = rnd.NextDouble() + 0.2; + Vector3 velocity = new Vector3( (float)velX, (float)velY, (float)velZ ); + + double xOffset = rnd.NextDouble() - 0.5; // [-0.5, 0.5] + double yOffset = (rnd.NextDouble() - 0.125) * blockHeight; + double zOffset = rnd.NextDouble() - 0.5; + Vector3 pos = startPos + new Vector3( 0.5f + (float)xOffset, + (float)yOffset, 0.5f + (float)zOffset ); + + TextureRec particleRec = rec; + particleRec.U1 = rec.U1 + rnd.Next( 0, cellsCount ) * elemSize; + particleRec.V1 = rec.V1 + rnd.Next( 0, cellsCount ) * elemSize; + particleRec.U2 = particleRec.U1 + elemSize; + particleRec.V2 = particleRec.V1 + elemSize; + double life = 1.5 - rnd.NextDouble(); + + terrainParticles.Add( new TerrainParticle( game, pos, velocity, life, particleRec ) ); + } + } + + public void AddRainParticle( Vector3 pos ) { + Vector3 startPos = pos; + + for( int i = 0; i < 5; i++ ) { + double velX = rnd.NextDouble() * 0.8 - 0.4; // [-0.4, 0.4] + double velZ = rnd.NextDouble() * 0.8 - 0.4; + double velY = rnd.NextDouble() + 0.5; + Vector3 velocity = new Vector3( (float)velX, (float)velY, (float)velZ ); + + double xOffset = rnd.NextDouble() - 0.5; // [-0.5, 0.5] + double yOffset = 0.01; + double zOffset = rnd.NextDouble() - 0.5; + pos = startPos + new Vector3( 0.5f + (float)xOffset, + (float)yOffset, 0.5f + (float)zOffset ); + double life = 3.5 - rnd.NextDouble(); + rainParticles.Add( new RainParticle( game, pos, velocity, life ) ); + } + } + } +} diff --git a/ClassicalSharp/Particles/RainParticle.cs b/ClassicalSharp/Particles/RainParticle.cs new file mode 100644 index 000000000..abfdef5f4 --- /dev/null +++ b/ClassicalSharp/Particles/RainParticle.cs @@ -0,0 +1,28 @@ +using System; +using OpenTK; + +namespace ClassicalSharp.Particles { + + public sealed class RainParticle : CollidableParticle { + + static Vector2 rainSize = new Vector2( 1/8f, 1/8f ); + static TextureRec rec = new TextureRec( 2/128f, 14/128f, 3/128f, 2/128f ); + public RainParticle( Game game, Vector3 pos, Vector3 velocity, double lifetime ) + : base( game, pos, velocity, lifetime ) { + } + + public override void Render( double delta, float t, VertexPos3fTex2fCol4b[] vertices, ref int index ) { + Position = Vector3.Lerp( lastPos, nextPos, t ); + Vector3 p111, p121, p212, p222; + Utils.CalcBillboardPoints( rainSize, Position, ref game.View, + out p111, out p121, out p212, out p222 ); + World map = game.World; + FastColour col = map.IsLit( Vector3I.Floor( Position ) ) ? map.Sunlight : map.Shadowlight; + + vertices[index++] = new VertexPos3fTex2fCol4b( p111, rec.U1, rec.V2, col ); + vertices[index++] = new VertexPos3fTex2fCol4b( p121, rec.U1, rec.V1, col ); + vertices[index++] = new VertexPos3fTex2fCol4b( p222, rec.U2, rec.V1, col ); + vertices[index++] = new VertexPos3fTex2fCol4b( p212, rec.U2, rec.V2, col ); + } + } +} diff --git a/ClassicalSharp/Particles/TerrainParticle.cs b/ClassicalSharp/Particles/TerrainParticle.cs new file mode 100644 index 000000000..2ec081577 --- /dev/null +++ b/ClassicalSharp/Particles/TerrainParticle.cs @@ -0,0 +1,29 @@ +using System; +using OpenTK; + +namespace ClassicalSharp.Particles { + + public sealed class TerrainParticle : CollidableParticle { + + static Vector2 terrainSize = new Vector2( 1/8f, 1/8f ); + TextureRec rec; + public TerrainParticle( Game game, Vector3 pos, Vector3 velocity, double lifetime, TextureRec rec ) + : base( game, pos, velocity, lifetime ) { + this.rec = rec; + } + + public override void Render( double delta, float t, VertexPos3fTex2fCol4b[] vertices, ref int index ) { + Position = Vector3.Lerp( lastPos, nextPos, t ); + Vector3 p111, p121, p212, p222; + Utils.CalcBillboardPoints( terrainSize, Position, ref game.View, + out p111, out p121, out p212, out p222 ); + World map = game.World; + FastColour col = map.IsLit( Vector3I.Floor( Position ) ) ? map.Sunlight : map.Shadowlight; + + vertices[index++] = new VertexPos3fTex2fCol4b( p111, rec.U1, rec.V2, col ); + vertices[index++] = new VertexPos3fTex2fCol4b( p121, rec.U1, rec.V1, col ); + vertices[index++] = new VertexPos3fTex2fCol4b( p222, rec.U2, rec.V1, col ); + vertices[index++] = new VertexPos3fTex2fCol4b( p212, rec.U2, rec.V2, col ); + } + } +} diff --git a/ClassicalSharp/SharpWave.dll b/ClassicalSharp/SharpWave.dll new file mode 100644 index 000000000..021aa796b Binary files /dev/null and b/ClassicalSharp/SharpWave.dll differ diff --git a/ClassicalSharp/SharpWave.dll.config b/ClassicalSharp/SharpWave.dll.config new file mode 100644 index 000000000..a4e4f7b60 --- /dev/null +++ b/ClassicalSharp/SharpWave.dll.config @@ -0,0 +1,4 @@ + + + + diff --git a/Launcher2/Gui/Screens/ResourcesScreen.cs b/Launcher2/Gui/Screens/ResourcesScreen.cs index d58eef229..b1681a077 100644 --- a/Launcher2/Gui/Screens/ResourcesScreen.cs +++ b/Launcher2/Gui/Screens/ResourcesScreen.cs @@ -48,10 +48,9 @@ namespace Launcher2 { game.Downloader = new AsyncDownloader( "null" ); if( fetcher != null ) return; - fetcher = new ResourceFetcher( game.Downloader ); - fetcher.DownloadItems( SetStatus ); - selectedWidget = null; - + fetcher = game.fetcher; + fetcher.DownloadItems( game.Downloader, SetStatus ); + selectedWidget = null; Resize(); } @@ -59,7 +58,7 @@ namespace Launcher2 { static FastColour backCol = new FastColour( 120, 85, 151 ); static readonly string mainText = "Some required resources weren't found" + Environment.NewLine + "Okay to download them?"; - static readonly string format = "Estimated size: {0} megabytes"; + static readonly string format = "Download size: {0} megabytes"; static FastColour clearCol = new FastColour( 12, 12, 12 ); void Draw() { @@ -68,8 +67,9 @@ namespace Launcher2 { drawer.DrawRect( backCol, game.Width / 2 - 175, game.Height / 2 - 70, 175 * 2, 70 * 2 ); + float dataSize = game.fetcher.DownloadSize; string text = widgets[0] != null ? widgets[0].Text - : String.Format( format, ResourceFetcher.EstimateDownloadSize() ); + : String.Format( format, dataSize.ToString( "F2" ) ); MakeLabelAt( text, statusFont, Anchor.Centre, Anchor.Centre, 0, 5 ); // Clear the entire previous widgets state. diff --git a/Launcher2/Launcher2.csproj b/Launcher2/Launcher2.csproj index 9b9fba424..2849587f0 100644 --- a/Launcher2/Launcher2.csproj +++ b/Launcher2/Launcher2.csproj @@ -45,6 +45,9 @@ obj\ + + ..\ClassicalSharp\SharpWave.dll + @@ -72,6 +75,7 @@ + diff --git a/Launcher2/LauncherWindow.cs b/Launcher2/LauncherWindow.cs index cd186110d..7ac4c3436 100644 --- a/Launcher2/LauncherWindow.cs +++ b/Launcher2/LauncherWindow.cs @@ -48,6 +48,8 @@ namespace Launcher2 { public Dictionary> ScreenMetadata = new Dictionary>(); + internal ResourceFetcher fetcher; + Font logoFont, logoItalicFont; PlatformDrawer platformDrawer; public void Init() { @@ -120,7 +122,9 @@ namespace Launcher2 { TryLoadTexturePack(); platformDrawer.Init( Window.WindowInfo ); - if( !ResourceFetcher.CheckAllResourcesExist() ) { + fetcher = new ResourceFetcher(); + fetcher.CheckResourceExistence(); + if( !fetcher.AllResourcesExist ) { SetScreen( new ResourcesScreen( this ) ); } else { SetScreen( new MainScreen( this ) ); diff --git a/Launcher2/Patcher/ResourceFetcher.cs b/Launcher2/Patcher/ResourceFetcher.cs index f6a884e4b..0c519e588 100644 --- a/Launcher2/Patcher/ResourceFetcher.cs +++ b/Launcher2/Patcher/ResourceFetcher.cs @@ -7,17 +7,22 @@ namespace Launcher2 { public sealed class ResourceFetcher { public bool Done = false; - AsyncDownloader downloader; - public ResourceFetcher( AsyncDownloader downloader ) { - this.downloader = downloader; + internal AsyncDownloader downloader; + public ResourceFetcher() { + digPath = Path.Combine( "audio", "dig" ); + stepPath = Path.Combine( "audio", "step" ); } const string jarClassicUri = "http://s3.amazonaws.com/Minecraft.Download/versions/c0.30_01c/c0.30_01c.jar"; const string jar162Uri = "http://s3.amazonaws.com/Minecraft.Download/versions/1.6.2/1.6.2.jar"; const string pngTerrainPatchUri = "http://static.classicube.net/terrain-patch.png"; const string pngGuiPatchUri = "http://static.classicube.net/gui.png"; + const string digSoundsUri = "http://s3.amazonaws.com/MinecraftResources/sound3/dig/"; + const string stepSoundsUri = "http://s3.amazonaws.com/MinecraftResources/sound3/step/"; + const string musicUri = "http://s3.amazonaws.com/MinecraftResources/music/"; - public void DownloadItems( Action setStatus ) { + public void DownloadItems( AsyncDownloader downloader, Action setStatus ) { + this.downloader = downloader; downloader.DownloadData( jarClassicUri, false, "classic_jar" ); downloader.DownloadData( jar162Uri, false, "162_jar" ); downloader.DownloadData( pngTerrainPatchUri, false, "terrain_patch" ); @@ -69,14 +74,50 @@ namespace Launcher2 { return true; } - public static bool CheckAllResourcesExist() { - return File.Exists( "default.zip" ); + public void CheckResourceExistence() { + AllResourcesExist = File.Exists( "default.zip" ); + //&& Directory.Exists( "audio" ) && File.Exists( digPath + ".bin" ) + //&& File.Exists( stepPath + ".bin" ); + + if( !File.Exists( "default.zip" ) ) { + // classic.jar + 1.6.2.jar + terrain-patch.png + gui.png + DownloadSize += (291 + 4621 + 7 + 21) / 1024f; + ResourcesCount += 4; + } + + for( int i = 0; i < musicFiles.Length; i++ ) { + string file = Path.Combine( "audio", musicFiles[i] + ".ogg" ); + musicExists[i] = File.Exists( file ); + continue; + // TODO: download music files + if( !musicExists[i] ) { + DownloadSize += musicSizes[i] / 1024f; + ResourcesCount++; + } + } + ResourcesCount += digSounds.Length; + ResourcesCount += stepSounds.Length; } + public bool AllResourcesExist; + public float DownloadSize; + public int ResourcesCount; - public static string EstimateDownloadSize() { - float size = (291 + 4621 + 7 + 21) / 1024f; - // classic.jar + 1.6.2.jar + terrain-patch.png + gui.png - return size.ToString( "F2" ); - } + string digPath, stepPath; + string[] digSounds = new [] { "cloth1", "cloth2", "cloth3", "cloth4", + "glass1", "glass2", "glass3", "glass4", "grass1", "grass2", "grass3", + "grass4", "gravel1", "gravel2", "gravel3", "gravel4", "sand1", "sand2", + "sand3", "sand4", "snow1", "snow2", "snow3", "snow4", "stone1", "stone2", + "stone3", "stone4", "wood1", "wood2", "wood3", "wood4" }; + + string[] stepSounds = new [] { "cloth1", "cloth2", "cloth3", "cloth4", "grass1", + "grass2", "grass3", "grass4", "grass5", "grass6", "gravel1", "gravel2", + "gravel3", "gravel4", "sand1", "sand2", "sand3", "sand4", "sand5", "snow1", + "snow2", "snow3", "snow4", "stone1", "stone2", "stone3", "stone4", "stone5", + "stone6", "wood1", "wood2", "wood3", "wood4", "wood5", "wood6" }; + + string[] musicFiles = new [] { "calm1", "calm2", + "calm3", "hal1", "hal2", "hal3" }; + int[] musicSizes = new [] { 2472, 1931, 2181, 1926, 1714, 1879, 2499 }; + bool[] musicExists = new bool[6]; } } diff --git a/Launcher2/Patcher/SoundPatcher.cs b/Launcher2/Patcher/SoundPatcher.cs new file mode 100644 index 000000000..3596610d3 --- /dev/null +++ b/Launcher2/Patcher/SoundPatcher.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using ClassicalSharp.Network; +using SharpWave; +using SharpWave.Codecs; +using SharpWave.Codecs.Vorbis; +using SharpWave.Containers; + +namespace Launcher2 { + + public sealed class SoundPatcher { + + string[] files, identifiers; + string prefix; + FileStream outData; + StreamWriter outText; + RawOut outDecoder; + + public SoundPatcher( string[] files, string prefix, string outputPath ) { + this.files = files; + this.prefix = prefix; + InitOutput( outputPath ); + } + + public void FetchFiles( string baseUrl, ResourceFetcher fetcher ) { + identifiers = new string[files.Length]; + for( int i = 0; i < files.Length; i++ ) + identifiers[i] = prefix + files[i]; + + for( int i = 0; i < files.Length; i++ ) { + string url = baseUrl + files[i] + ".ogg"; + fetcher.downloader.DownloadData( url, false, identifiers[i] ); + } + } + + public bool CheckDownloaded( ResourceFetcher fetcher, Action setStatus ) { + for( int i = 0; i < identifiers.Length; i++ ) { + DownloadedItem item; + if( fetcher.downloader.TryGetItem( identifiers[i], out item ) ) { + Console.WriteLine( "found sound " + identifiers[i] ); + if( item.Data == null ) { + setStatus( "&cFailed to download " + identifiers[i] ); + return false; + } + DecodeSound( files[i], (byte[])item.Data ); + + // TODO: setStatus( next ); + if( i == identifiers.Length - 1 ) { + Dispose(); + } + } + } + return true; + } + + void DecodeSound( string name, byte[] rawData ) { + long start = outData.Position; + using( MemoryStream ms = new MemoryStream( rawData ) ) { + OggContainer container = new OggContainer( ms ); + outDecoder.PlayStreaming( container ); + } + + long len = outData.Position - start; + outText.WriteLine( format, name, outDecoder.Frequency, + outDecoder.BitsPerSample, outDecoder.Channels, + start, len ); + } + + const string format = "{0},{1},{2},{3},{4},{5}"; + void InitOutput( string outputPath ) { + outData = File.Create( outputPath + ".bin" ); + outText = new StreamWriter( outputPath + ".txt" ); + outDecoder = new RawOut( outData, true ); + + outText.WriteLine( "# This file indicates where the various raw decompressed sound data " + + "are found in the corresponding raw .bin file." ); + outText.WriteLine( "# Each line is in the following format:" ); + outText.WriteLine( "# Identifier, Frequency/Sample rate, BitsPerSample, " + + "Channels, Offset from start, Length in bytes" ); + } + + void Dispose() { + outDecoder.Dispose(); + outData.Close(); + outText.Close(); + } + } +}