using System; using System.Drawing; using System.IO; using System.Net; using ClassicalSharp.Commands; using ClassicalSharp.GraphicsAPI; using ClassicalSharp.Model; using ClassicalSharp.Network; using ClassicalSharp.Particles; using ClassicalSharp.Renderers; using ClassicalSharp.Selections; using OpenTK; using OpenTK.Input; namespace ClassicalSharp { // TODO: Rewrite this so it isn't tied to GameWindow. (so we can use DirectX as backend) public partial class Game : GameWindow { public IGraphicsApi Graphics; public Map Map; public INetworkProcessor Network; public EntityList Players = new EntityList(); public CpeListInfo[] CpePlayersList = new CpeListInfo[256]; public LocalPlayer LocalPlayer; public Camera Camera; Camera firstPersonCam, thirdPersonCam; public BlockInfo BlockInfo; public double accumulator; public TerrainAtlas2D TerrainAtlas; public TerrainAtlas1D TerrainAtlas1D; public SkinType DefaultPlayerSkinType; public int ChunkUpdates; public MapRenderer MapRenderer; public MapEnvRenderer MapEnvRenderer; public EnvRenderer EnvRenderer; public WeatherRenderer WeatherRenderer; public CommandManager CommandManager; public SelectionManager SelectionManager; public ParticleManager ParticleManager; public PickingRenderer Picking; public PickedPos SelectedPos = new PickedPos(); public ModelCache ModelCache; internal string skinServer, chatInInputBuffer; internal int defaultIb; public bool CanUseThirdPersonCamera = true; FpsScreen fpsScreen; int hotbarIndex = 0; public bool CanChangeHeldBlock = true; public Block[] Hotbar; public int HeldBlockIndex { get { return hotbarIndex; } set { if( !CanChangeHeldBlock ) { AddChat( "&e/client: &cThe server has forbidden you from changing your held block." ); return; } hotbarIndex = value; RaiseHeldBlockChanged(); } } public Block HeldBlock { get { return Hotbar[hotbarIndex]; } set { if( !CanChangeHeldBlock ) { AddChat( "&e/client: &cThe server has forbidden you from changing your held block." ); return; } for( int i = 0; i < Hotbar.Length; i++ ) { if( Hotbar[i] == value ) { hotbarIndex = i; RaiseHeldBlockChanged(); return; } } Hotbar[hotbarIndex] = value; RaiseHeldBlockChanged(); } } public bool[] CanPlace = new bool[256]; public bool[] CanDelete = new bool[256]; public IPAddress IPAddress; public string Username; public string Mppass; public int Port; public int ViewDistance = 512; public long Vertices; public FrustumCulling Culling; int width, height; public AsyncDownloader AsyncDownloader; public Matrix4 View, Projection; public int MouseSensitivity = 30; void LoadAtlas( Bitmap bmp ) { TerrainAtlas1D.Dispose(); TerrainAtlas.Dispose(); TerrainAtlas.UpdateState( bmp ); TerrainAtlas1D.UpdateState( TerrainAtlas ); } public void ChangeTerrainAtlas( Bitmap newAtlas ) { LoadAtlas( newAtlas ); Raise( TerrainAtlasChanged ); } public Game() : base() { // We can't use enum array initaliser because this causes problems when building with mono // and running on default .NET (https://bugzilla.xamarin.com/show_bug.cgi?id=572) #if !__MonoCS__ Hotbar = new Block[] { Block.Stone, Block.Cobblestone, Block.Brick, Block.Dirt, Block.WoodenPlanks, Block.Wood, Block.Leaves, Block.Glass, Block.Slab }; #else Hotbar = new Block[9]; Hotbar[0] = Block.Stone; Hotbar[1] = Block.Cobblestone; Hotbar[2] = Block.Brick; Hotbar[3] = Block.Dirt; Hotbar[4] = Block.WoodenPlanks; Hotbar[5] = Block.Wood; Hotbar[6] = Block.Leaves; Hotbar[7] = Block.Glass; Hotbar[8] = Block.Slab; #endif } protected override void OnLoad( EventArgs e ) { #if !USE_DX Graphics = new OpenGLApi(); #else Graphics = new Direct3D9Api( this ); #endif defaultIb = Graphics.MakeDefaultIb(); ModelCache = new ModelCache( this ); ModelCache.InitCache(); AsyncDownloader = new AsyncDownloader( skinServer ); Graphics.PrintGraphicsInfo(); Bitmap terrainBmp = new Bitmap( "terrain.png" ); TerrainAtlas1D = new TerrainAtlas1D( Graphics ); TerrainAtlas = new TerrainAtlas2D( Graphics ); LoadAtlas( terrainBmp ); BlockInfo = new BlockInfo(); BlockInfo.Init(); BlockInfo.SetDefaultBlockPermissions( CanPlace, CanDelete ); Map = new Map( this ); LocalPlayer = new LocalPlayer( this ); Players[255] = LocalPlayer; width = Width; height = Height; MapRenderer = new MapRenderer( this ); MapEnvRenderer = new MapEnvRenderer( this ); EnvRenderer = new StandardEnvRenderer( this ); if( IPAddress == null ) { Network = new Singleplayer.SinglePlayerServer( this ); } else { Network = new NetworkProcessor( this ); } firstPersonCam = new FirstPersonCamera( this ); thirdPersonCam = new ThirdPersonCamera( this ); Camera = firstPersonCam; CommandManager = new CommandManager(); CommandManager.Init( this ); SelectionManager = new SelectionManager( this ); ParticleManager = new ParticleManager( this ); WeatherRenderer = new WeatherRenderer( this ); WeatherRenderer.Init(); Graphics.SetVSync( this, true ); Graphics.DepthTest = true; Graphics.DepthTestFunc( CompareFunc.LessEqual ); //Graphics.DepthWrite = true; Graphics.AlphaBlendFunc( BlendFunc.SourceAlpha, BlendFunc.InvSourceAlpha ); Graphics.AlphaTestFunc( CompareFunc.Greater, 0.5f ); RegisterInputHandlers(); Title = Utils.AppName; fpsScreen = new FpsScreen( this ); fpsScreen.Init(); Culling = new FrustumCulling(); EnvRenderer.Init(); MapEnvRenderer.Init(); Picking = new PickingRenderer( this ); string connectString = "Connecting to " + IPAddress + ":" + Port + ".."; SetNewScreen( new LoadingMapScreen( this, connectString, "Reticulating splines" ) ); Network.Connect( IPAddress, Port ); } public void SetViewDistance( int distance ) { ViewDistance = distance; Utils.LogDebug( "setting view distance to: " + distance ); Raise( ViewDistanceChanged ); UpdateProjection(); } /// Gets whether the active screen handles all input. public bool ScreenLockedInput { get { return activeScreen != null && activeScreen.HandlesAllInput; } } const int ticksFrequency = 20; const double ticksPeriod = 1.0 / ticksFrequency; const double imageCheckPeriod = 30.0; double ticksAccumulator = 0, imageCheckAccumulator = 0; protected override void OnRenderFrame( FrameEventArgs e ) { Graphics.BeginFrame( this ); Graphics.BindIb( defaultIb ); accumulator += e.Time; imageCheckAccumulator += e.Time; ticksAccumulator += e.Time; Vertices = 0; if( !Focused && ( activeScreen == null || !activeScreen.HandlesAllInput ) ) { SetNewScreen( new PauseScreen( this ) ); } if( imageCheckAccumulator > imageCheckPeriod ) { imageCheckAccumulator -= imageCheckPeriod; AsyncDownloader.PurgeOldEntries( 10 ); } base.OnRenderFrame( e ); int ticksThisFrame = 0; while( ticksAccumulator >= ticksPeriod ) { Network.Tick( ticksPeriod ); Players.Tick( ticksPeriod ); Camera.Tick( ticksPeriod ); ParticleManager.Tick( ticksPeriod ); ticksThisFrame++; ticksAccumulator -= ticksPeriod; } if( ticksThisFrame > ticksFrequency / 3 ) { Utils.LogWarning( "Falling behind (did {0} ticks this frame)", ticksThisFrame ); } float t = (float)( ticksAccumulator / ticksPeriod ); LocalPlayer.SetInterpPosition( t ); Graphics.Clear(); Graphics.SetMatrixMode( MatrixType.Modelview ); Matrix4 modelView = Camera.GetView(); View = modelView; Graphics.LoadMatrix( ref modelView ); Culling.CalcFrustumEquations( ref Projection, ref modelView ); bool visible = activeScreen == null || !activeScreen.BlocksWorld; if( visible ) { Players.Render( e.Time, t ); ParticleManager.Render( e.Time, t ); Camera.GetPickedBlock( SelectedPos ); // TODO: only pick when necessary if( SelectedPos.Valid ) Picking.Render( e.Time, SelectedPos ); EnvRenderer.Render( e.Time ); MapRenderer.Render( e.Time ); WeatherRenderer.Render( e.Time ); SelectionManager.Render( e.Time ); bool left = IsMousePressed( MouseButton.Left ); bool right = IsMousePressed( MouseButton.Right ); bool middle = IsMousePressed( MouseButton.Middle ); PickBlocks( true, left, right, middle ); } else { SelectedPos.SetAsInvalid(); } Graphics.Mode2D( Width, Height ); fpsScreen.Render( e.Time ); if( activeScreen != null ) { activeScreen.Render( e.Time ); } Graphics.Mode3D(); if( screenshotRequested ) { if( !Directory.Exists( "screenshots" ) ) { Directory.CreateDirectory( "screenshots" ); } string timestamp = DateTime.Now.ToString( "dd-MM-yyyy-HH-mm-ss" ); string path = Path.Combine( "screenshots", "screenshot_" + timestamp + ".png" ); Graphics.TakeScreenshot( path, ClientSize ); screenshotRequested = false; } Graphics.EndFrame( this ); } public override void Dispose() { MapRenderer.Dispose(); MapEnvRenderer.Dispose(); EnvRenderer.Dispose(); WeatherRenderer.Dispose(); SetNewScreen( null ); fpsScreen.Dispose(); SelectionManager.Dispose(); TerrainAtlas.Dispose(); TerrainAtlas1D.Dispose(); ModelCache.Dispose(); Picking.Dispose(); ParticleManager.Dispose(); Players.Dispose(); AsyncDownloader.Dispose(); if( writer != null ) { writer.Close(); } if( activeScreen != null ) { activeScreen.Dispose(); } Graphics.DeleteIb( defaultIb ); Graphics.Dispose(); Utils2D.Dispose(); base.Dispose(); } public void UpdateProjection() { Matrix4 projection = Camera.GetProjection(); Projection = projection; Graphics.SetMatrixMode( MatrixType.Projection ); Graphics.LoadMatrix( ref projection ); Graphics.SetMatrixMode( MatrixType.Modelview ); } protected override void OnResize( EventArgs e ) { base.OnResize( e ); Graphics.OnWindowResize( this ); UpdateProjection(); if( activeScreen != null ) { activeScreen.OnResize( width, height, Width, Height ); } width = Width; height = Height; } public void Disconnect( string title, string reason ) { SetNewScreen( new ErrorScreen( this, title, reason ) ); Map.Reset(); Map.mapData = null; GC.Collect(); } Screen activeScreen; public void SetNewScreen( Screen screen ) { if( activeScreen != null ) { activeScreen.Dispose(); } if( activeScreen != null && activeScreen.HandlesAllInput ) { Camera.RegrabMouse(); lastClick = DateTime.UtcNow; } activeScreen = screen; if( screen != null ) { screen.game = this; screen.Init(); } if( Network.UsingPlayerClick ) { byte targetId = Players.GetClosetPlayer( this ); ButtonStateChanged( MouseButton.Left, false, targetId ); ButtonStateChanged( MouseButton.Right, false, targetId ); ButtonStateChanged( MouseButton.Middle, false, targetId ); } } public void SetCamera( bool thirdPerson ) { PerspectiveCamera oldCam = (PerspectiveCamera)Camera; Camera = ( thirdPerson && CanUseThirdPersonCamera ) ? thirdPersonCam : firstPersonCam; PerspectiveCamera newCam = (PerspectiveCamera)Camera; newCam.delta = oldCam.delta; newCam.previous = oldCam.previous; UpdateProjection(); } public void UpdateBlock( int x, int y, int z, byte block ) { int oldHeight = Map.GetLightHeight( x, z ); Map.SetBlock( x, y, z, block ); int newHeight = Map.GetLightHeight( x, z ); MapRenderer.RedrawBlock( x, y, z, block, oldHeight, newHeight ); } } public sealed class CpeListInfo { public byte NameId; /// Unformatted name of the player for autocompletion, etc. /// Colour codes are always removed from this. public string PlayerName; /// Formatted name for display in the player list. /// Can include colour codes. public string ListName; /// Name of the group this player is in. /// Can include colour codes. public string GroupName; /// Player's rank within the group. (0 is highest) /// Multiple group members can share the same rank, /// so a player's group rank is not a unique identifier. public byte GroupRank; public CpeListInfo( byte id, string playerName, string listName, string groupName, byte groupRank ) { NameId = id; PlayerName = playerName; ListName = listName; GroupName = groupName; GroupRank = groupRank; } } }