// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using System.Diagnostics; using System.Drawing; using System.IO; using System.Net; using System.Threading; using ClassicalSharp.Audio; using ClassicalSharp.Commands; using ClassicalSharp.Entities; using ClassicalSharp.Events; using ClassicalSharp.GraphicsAPI; using ClassicalSharp.Gui.Screens; using ClassicalSharp.Map; using ClassicalSharp.Model; using ClassicalSharp.Network; using ClassicalSharp.Particles; using ClassicalSharp.Renderers; using ClassicalSharp.Selections; using ClassicalSharp.Textures; using OpenTK; using OpenTK.Input; #if ANDROID using Android.Graphics; #endif using PathIO = System.IO.Path; // Android.Graphics.Path clash otherwise namespace ClassicalSharp { public partial class Game : IDisposable { // For FileStreams we need to keep it open for lifetime of the image Stream lastAtlas; void LoadAtlas( Bitmap bmp, Stream data ) { TerrainAtlas1D.Dispose(); TerrainAtlas.Dispose(); if( lastAtlas != null ) lastAtlas.Dispose(); TerrainAtlas.UpdateState( BlockInfo, bmp ); TerrainAtlas1D.UpdateState( TerrainAtlas ); lastAtlas = data; } public bool ChangeTerrainAtlas( Bitmap atlas, Stream data ) { bool pow2 = Utils.IsPowerOf2( atlas.Width ) && Utils.IsPowerOf2( atlas.Height ); if( !pow2 || atlas.Width != atlas.Height ) { Chat.Add( "&cCurrent texture pack has an invalid terrain.png" ); Chat.Add( "&cWidth and length must be the same, and also powers of two." ); return false; } LoadAtlas( atlas, data ); Events.RaiseTerrainAtlasChanged(); return true; } public void Run() { window.Run(); } public void Exit() { window.Exit(); } void OnNewMapCore( object sender, EventArgs e ) { for( int i = 0; i < Components.Count; i++ ) Components[i].OnNewMap( this ); } void OnNewMapLoadedCore( object sender, EventArgs e ) { for( int i = 0; i < Components.Count; i++ ) Components[i].OnNewMapLoaded( this ); } public T AddComponent( T obj ) where T : IGameComponent { Components.Add( obj ); return obj; } public bool ReplaceComponent( ref T old, T obj ) where T : IGameComponent { for( int i = 0; i < Components.Count; i++ ) { if( !object.ReferenceEquals( Components[i], old ) ) continue; old.Dispose(); Components[i] = obj; old = obj; obj.Init( this ); return true; } Components.Add( obj ); obj.Init( this ); return false; } public void SetViewDistance( float distance, bool userDist ) { if( userDist ) { UserViewDistance = distance; Options.Set( OptionsKey.ViewDist, distance ); } distance = Math.Min( distance, MaxViewDistance ); if( distance == ViewDistance ) return; ViewDistance = distance; Events.RaiseViewDistanceChanged(); UpdateProjection(); } Stopwatch frameTimer = new Stopwatch(); internal void RenderFrame( double delta ) { frameTimer.Reset(); frameTimer.Start(); Graphics.BeginFrame( this ); Graphics.BindIb( defaultIb ); accumulator += delta; Vertices = 0; if( !Focused && !Gui.ActiveScreen.HandlesAllInput ) Gui.SetNewScreen( new PauseScreen( this ) ); CheckZoomFov(); DoScheduledTasks( delta ); float t = (float)(entTask.Accumulator / entTask.Interval); LocalPlayer.SetInterpPosition( t ); if( !SkipClear || SkyboxRenderer.ShouldRender ) Graphics.Clear(); CurrentCameraPos = Camera.GetCameraPos( t ); UpdateViewMatrix(); bool visible = Gui.activeScreen == null || !Gui.activeScreen.BlocksWorld; if( World.IsNotLoaded ) visible = false; if( visible ) { Render3D( delta, t ); } else { SelectedPos.SetAsInvalid(); } Gui.Render( delta ); if( screenshotRequested ) TakeScreenshot(); Graphics.EndFrame( this ); LimitFPS(); } void CheckZoomFov() { bool allowZoom = Gui.activeScreen == null && !Gui.hudScreen.HandlesAllInput; if( allowZoom && IsKeyDown( KeyBind.ZoomScrolling ) ) InputHandler.SetFOV( ZoomFov, false ); } void UpdateViewMatrix() { Graphics.SetMatrixMode( MatrixType.Modelview ); Matrix4 modelView = Camera.GetView(); View = modelView; Graphics.LoadMatrix( ref modelView ); Culling.CalcFrustumEquations( ref Projection, ref modelView ); } void Render3D( double delta, float t ) { if( SkyboxRenderer.ShouldRender ) SkyboxRenderer.Render( delta ); AxisLinesRenderer.Render( delta ); Entities.RenderModels( Graphics, delta, t ); Entities.RenderNames( Graphics, delta ); ParticleManager.Render( delta, t ); Camera.GetPickedBlock( SelectedPos ); // TODO: only pick when necessary EnvRenderer.Render( delta ); MapRenderer.Render( delta ); MapBordersRenderer.RenderSides( delta ); if( SelectedPos.Valid && !HideGui ) { Picking.UpdateState( SelectedPos ); Picking.Render( delta ); } // Render water over translucent blocks when underwater for proper alpha blending Vector3 pos = LocalPlayer.Position; if( CurrentCameraPos.Y < World.Env.EdgeHeight && (pos.X < 0 || pos.Z < 0 || pos.X > World.Width || pos.Z > World.Length) ) { MapRenderer.RenderTranslucent( delta ); MapBordersRenderer.RenderEdges( delta ); } else { MapBordersRenderer.RenderEdges( delta ); MapRenderer.RenderTranslucent( delta ); } // Need to render again over top of translucent block, as the selection outline // is drawn without writing to the depth buffer if( SelectedPos.Valid && !HideGui && BlockInfo.IsTranslucent[SelectedPos.Block] ) Picking.Render( delta ); Entities.DrawShadows(); SelectionManager.Render( delta ); Entities.RenderHoveredNames( Graphics, delta ); bool left = IsMousePressed( MouseButton.Left ); bool middle = IsMousePressed( MouseButton.Middle ); bool right = IsMousePressed( MouseButton.Right ); InputHandler.PickBlocks( true, left, middle, right ); if( !HideGui ) HeldBlockRenderer.Render( delta ); } void DoScheduledTasks( double time ) { for( int i = 0; i < Tasks.Count; i++ ) { ScheduledTask task = Tasks[i]; task.Accumulator += time; while( task.Accumulator >= task.Interval ) { task.Callback( task ); task.Accumulator -= task.Interval; } } } public ScheduledTask AddScheduledTask( double interval, Action callback ) { ScheduledTask task = new ScheduledTask(); task.Interval = interval; task.Callback = callback; Tasks.Add( task ); return task; } void TakeScreenshot() { string path = PathIO.Combine( Program.AppDirectory, "screenshots" ); if( !Directory.Exists( path ) ) Directory.CreateDirectory( path ); string timestamp = DateTime.Now.ToString( "dd-MM-yyyy-HH-mm-ss" ); string file = "screenshot_" + timestamp + ".png"; path = PathIO.Combine( path, file ); Graphics.TakeScreenshot( path, Width, Height ); Chat.Add( "&eTaken screenshot as: " + file ); screenshotRequested = false; } public void UpdateProjection() { DefaultFov = Options.GetInt( OptionsKey.FieldOfView, 1, 150, 70 ); Matrix4 projection = Camera.GetProjection(); Projection = projection; Graphics.SetMatrixMode( MatrixType.Projection ); Graphics.LoadMatrix( ref projection ); Graphics.SetMatrixMode( MatrixType.Modelview ); Events.RaiseProjectionChanged(); } internal void OnResize() { Width = window.Width; Height = window.Height; Graphics.OnWindowResize( this ); UpdateProjection(); Gui.OnResize(); } public void Disconnect( string title, string reason ) { Gui.Reset( this ); World.Reset(); World.blocks = null; Drawer2D.InitColours(); BlockInfo.Reset( this ); TexturePack.ExtractDefault( this ); Gui.SetNewScreen( new ErrorScreen( this, title, reason ) ); GC.Collect(); } public void CycleCamera() { if( ClassicMode ) return; int i = Cameras.IndexOf( Camera ); i = (i + 1) % Cameras.Count; Camera = Cameras[i]; if( !LocalPlayer.Hacks.CanUseThirdPersonCamera || !LocalPlayer.Hacks.Enabled ) Camera = Cameras[0]; UpdateProjection(); } public void UpdateBlock( int x, int y, int z, byte block ) { int oldHeight = World.GetLightHeight( x, z ) + 1; World.SetBlock( x, y, z, block ); int newHeight = World.GetLightHeight( x, z ) + 1; MapRenderer.RedrawBlock( x, y, z, block, oldHeight, newHeight ); } float limitMilliseconds; public void SetFpsLimitMethod( FpsLimitMethod method ) { FpsLimit = method; limitMilliseconds = 0; Graphics.SetVSync( this, method == FpsLimitMethod.LimitVSync ); if( method == FpsLimitMethod.Limit120FPS ) limitMilliseconds = 1000f / 120; if( method == FpsLimitMethod.Limit60FPS ) limitMilliseconds = 1000f / 60; if( method == FpsLimitMethod.Limit30FPS ) limitMilliseconds = 1000f / 30; } void LimitFPS() { if( FpsLimit == FpsLimitMethod.LimitVSync ) return; double elapsed = frameTimer.Elapsed.TotalMilliseconds; double leftOver = limitMilliseconds - elapsed; if( leftOver > 0.001 ) // going faster than limit Thread.Sleep( (int)Math.Round( leftOver, MidpointRounding.AwayFromZero ) ); } public bool IsKeyDown( Key key ) { return InputHandler.IsKeyDown( key ); } public bool IsKeyDown( KeyBind binding ) { return InputHandler.IsKeyDown( binding ); } public bool IsMousePressed( MouseButton button ) { return InputHandler.IsMousePressed( button ); } public Key Mapping( KeyBind mapping ) { return InputHandler.Keys[mapping]; } public void Dispose() { MapRenderer.Dispose(); TerrainAtlas.Dispose(); TerrainAtlas1D.Dispose(); ModelCache.Dispose(); Entities.Dispose(); WorldEvents.OnNewMap -= OnNewMapCore; WorldEvents.OnNewMapLoaded -= OnNewMapLoadedCore; Events.TextureChanged -= TextureChangedCore; for( int i = 0; i < Components.Count; i++ ) Components[i].Dispose(); Graphics.DeleteIb( ref defaultIb ); Drawer2D.DisposeInstance(); Graphics.DeleteTexture( ref CloudsTex ); Graphics.Dispose(); if( lastAtlas != null ) lastAtlas.Dispose(); if( Options.OptionsChanged.Count == 0 ) return; Options.Load(); Options.Save(); } internal bool CanPick( byte block ) { if( BlockInfo.IsAir[block] ) return false; if( BlockInfo.IsSprite[block] ) return true; if( BlockInfo.Collide[block] != CollideType.SwimThrough ) return true; return !ModifiableLiquids ? false : Inventory.CanPlace[block] && Inventory.CanDelete[block]; } /// Reads a bitmap from the stream (converting it to 32 bits per pixel if necessary), /// and updates the native texture for it. public bool UpdateTexture( ref int texId, string file, byte[] data, bool setSkinType ) { MemoryStream stream = new MemoryStream( data ); int maxSize = Graphics.MaxTextureDimensions; using( Bitmap bmp = Platform.ReadBmp( stream ) ) { if( bmp.Width > maxSize || bmp.Height > maxSize ) { Chat.Add( "&cUnable to use " + file + " from the texture pack." ); Chat.Add( "&c Its size is (" + bmp.Width + "," + bmp.Height + "), your GPU supports (" + maxSize + "," + maxSize + ") at most." ); return false; } Graphics.DeleteTexture( ref texId ); if( setSkinType ) { DefaultPlayerSkinType = Utils.GetSkinType( bmp ); if( DefaultPlayerSkinType == SkinType.Invalid ) throw new NotSupportedException( "char.png has invalid dimensions" ); } if( !Platform.Is32Bpp( bmp ) ) { using( Bitmap bmp32 = Drawer2D.ConvertTo32Bpp( bmp ) ) texId = Graphics.CreateTexture( bmp32, true ); } else { texId = Graphics.CreateTexture( bmp, true ); } return true; } } public bool SetRenderType( string type ) { if( Utils.CaselessEquals( type, "legacyfast" ) ) { SetNewRenderType( true, true ); } else if( Utils.CaselessEquals( type, "legacy" ) ) { SetNewRenderType( true, false ); } else if( Utils.CaselessEquals( type, "normal" ) ) { SetNewRenderType( false, false ); } else if( Utils.CaselessEquals( type, "normalfast" ) ) { SetNewRenderType( false, true ); } else { return false; } Options.Set( OptionsKey.RenderType, type ); return true; } void SetNewRenderType( bool legacy, bool minimal ) { if( MapBordersRenderer == null ) { MapBordersRenderer = AddComponent( new MapBordersRenderer() ); MapBordersRenderer.legacy = legacy; } else { MapBordersRenderer.UseLegacyMode( legacy ); } if( minimal ) { if( EnvRenderer == null ) EnvRenderer = AddComponent( new MinimalEnvRenderer() ); else ReplaceComponent( ref EnvRenderer, new MinimalEnvRenderer() ); } else if( EnvRenderer == null ) { EnvRenderer = AddComponent( new StandardEnvRenderer() ); ((StandardEnvRenderer)EnvRenderer).legacy = legacy; } else { if( !(EnvRenderer is StandardEnvRenderer) ) ReplaceComponent( ref EnvRenderer, new StandardEnvRenderer() ); ((StandardEnvRenderer)EnvRenderer).UseLegacyMode( legacy ); } } void TextureChangedCore( object sender, TextureEventArgs e ) { byte[] data = e.Data; if( e.Name == "terrain.png" ) { MemoryStream stream = new MemoryStream( data ); Bitmap atlas = Platform.ReadBmp( stream ); if( ChangeTerrainAtlas( atlas, null ) ) return; atlas.Dispose(); } else if( e.Name == "cloud.png" || e.Name == "clouds.png" ) { UpdateTexture( ref CloudsTex, e.Name, data, false ); } else if( e.Name == "default.png" ) { MemoryStream stream = new MemoryStream( data ); Bitmap bmp = Platform.ReadBmp( stream ); if( !Platform.Is32Bpp( bmp ) ) Drawer2D.ConvertTo32Bpp( ref bmp ); Drawer2D.SetFontBitmap( bmp ); Events.RaiseChatFontChanged(); } } public Game( string username, string mppass, string skinServer, bool nullContext, int width, int height ) { window = new DesktopWindow( this, username, nullContext, width, height ); Username = username; Mppass = mppass; this.skinServer = skinServer; } } }