447 lines
14 KiB
C#

// 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>( T obj ) where T : IGameComponent {
Components.Add( obj );
return obj;
}
public bool ReplaceComponent<T>( 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<ScheduledTask> 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];
}
/// <summary> Reads a bitmap from the stream (converting it to 32 bits per pixel if necessary),
/// and updates the native texture for it. </summary>
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;
}
}
}