diff --git a/TrueCraft.Client/Input/KeyboardComponent.cs b/TrueCraft.Client/Input/KeyboardHandler.cs similarity index 96% rename from TrueCraft.Client/Input/KeyboardComponent.cs rename to TrueCraft.Client/Input/KeyboardHandler.cs index 06d92c9..e000a7b 100644 --- a/TrueCraft.Client/Input/KeyboardComponent.cs +++ b/TrueCraft.Client/Input/KeyboardHandler.cs @@ -8,7 +8,7 @@ namespace TrueCraft.Client.Input /// /// Encapsulates keyboard input in an event-driven manner. /// - public sealed class KeyboardComponent : GameComponent + public sealed class KeyboardHandler : GameComponent { /// /// Raised when a key for this keyboard component is pressed. @@ -29,7 +29,7 @@ namespace TrueCraft.Client.Input /// Creates a new keyboard component. /// /// The parent game for the component. - public KeyboardComponent(Game game) + public KeyboardHandler(Game game) : base(game) { } diff --git a/TrueCraft.Client/Input/MouseComponent.cs b/TrueCraft.Client/Input/MouseHandler.cs similarity index 98% rename from TrueCraft.Client/Input/MouseComponent.cs rename to TrueCraft.Client/Input/MouseHandler.cs index 79434b3..8ef25ad 100644 --- a/TrueCraft.Client/Input/MouseComponent.cs +++ b/TrueCraft.Client/Input/MouseHandler.cs @@ -7,7 +7,7 @@ namespace TrueCraft.Client.Input /// /// Encapsulates mouse input in an event-driven manner. /// - public sealed class MouseComponent : GameComponent + public sealed class MouseHandler : GameComponent { /// /// Raised when this mouse component is moved. @@ -38,7 +38,7 @@ namespace TrueCraft.Client.Input /// Creates a new mouse component. /// /// The parent game for the component. - public MouseComponent(Game game) + public MouseHandler(Game game) : base(game) { } diff --git a/TrueCraft.Client/Interface/ChatInterface.cs b/TrueCraft.Client/Interface/ChatInterface.cs index 20efcc9..ecc3d4e 100644 --- a/TrueCraft.Client/Interface/ChatInterface.cs +++ b/TrueCraft.Client/Interface/ChatInterface.cs @@ -103,7 +103,7 @@ namespace TrueCraft.Client.Interface public bool HasFocus { get; set; } public MultiplayerClient Client { get; set; } - public KeyboardComponent Keyboard { get; set; } + public KeyboardHandler Keyboard { get; set; } public FontRenderer Font { get; set; } private readonly object Lock = new object(); @@ -111,7 +111,7 @@ namespace TrueCraft.Client.Interface private List Messages { get; set; } private Texture2D DummyTexture { get; set; } - public ChatInterface(MultiplayerClient client, KeyboardComponent keyboard, FontRenderer font) + public ChatInterface(MultiplayerClient client, KeyboardHandler keyboard, FontRenderer font) { Client = client; Keyboard = keyboard; diff --git a/TrueCraft.Client/Interface/DebugInterface.cs b/TrueCraft.Client/Interface/DebugInterface.cs index 6623dbd..a7e66bb 100644 --- a/TrueCraft.Client/Interface/DebugInterface.cs +++ b/TrueCraft.Client/Interface/DebugInterface.cs @@ -14,7 +14,6 @@ namespace TrueCraft.Client.Interface public int Vertices { private get; set; } public int Chunks { private get; set; } - public Coordinates3D HighlightedBlock { private get; set; } public DebugInterface(MultiplayerClient client, FontRenderer font) { @@ -41,7 +40,6 @@ namespace TrueCraft.Client.Interface Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 1), string.Format("§o{0} vertices", Vertices), scale); Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 2), string.Format("§o{0} chunks", Chunks), scale); Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 3), string.Format("§o<{0:N2}, {1:N2}, {2:N2}>", Client.Position.X, Client.Position.Y, Client.Position.Z), scale); - Font.DrawText(spriteBatch, xOrigin, yOrigin + (yOffset * 4), string.Format("§o<{0:N2}, {1:N2}, {2:N2}>", HighlightedBlock.X, HighlightedBlock.Y, HighlightedBlock.Z), scale); } protected override void OnHide() { } diff --git a/TrueCraft.Client/Modules/ChunkModule.cs b/TrueCraft.Client/Modules/ChunkModule.cs new file mode 100644 index 0000000..bee6752 --- /dev/null +++ b/TrueCraft.Client/Modules/ChunkModule.cs @@ -0,0 +1,127 @@ +using System; +using TrueCraft.Client.Rendering; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using TrueCraft.API; + +namespace TrueCraft.Client.Modules +{ + public class ChunkModule : IGraphicalModule + { + public TrueCraftGame Game { get; set; } + public ChunkRenderer ChunkRenderer { get; set; } + + private List ChunkMeshes { get; set; } + private ConcurrentBag IncomingChunks { get; set; } + + private BasicEffect OpaqueEffect { get; set; } + private AlphaTestEffect TransparentEffect { get; set; } + + public ChunkModule(TrueCraftGame game) + { + Game = game; + + ChunkRenderer = new ChunkRenderer(Game.Client.World, Game, Game.BlockRepository); + Game.Client.ChunkLoaded += (sender, e) => ChunkRenderer.Enqueue(e.Chunk); + //Client.ChunkModified += (sender, e) => ChunkRenderer.Enqueue(e.Chunk, true); + ChunkRenderer.MeshCompleted += MeshCompleted; + ChunkRenderer.Start(); + + OpaqueEffect = new BasicEffect(Game.GraphicsDevice); + OpaqueEffect.EnableDefaultLighting(); + OpaqueEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3(); + OpaqueEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3(); + OpaqueEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3(); + OpaqueEffect.TextureEnabled = true; + OpaqueEffect.Texture = Game.TextureMapper.GetTexture("terrain.png"); + OpaqueEffect.FogEnabled = true; + OpaqueEffect.FogStart = 512f; + OpaqueEffect.FogEnd = 1000f; + OpaqueEffect.FogColor = Color.CornflowerBlue.ToVector3(); + OpaqueEffect.VertexColorEnabled = true; + + TransparentEffect = new AlphaTestEffect(Game.GraphicsDevice); + TransparentEffect.AlphaFunction = CompareFunction.Greater; + TransparentEffect.ReferenceAlpha = 127; + TransparentEffect.Texture = Game.TextureMapper.GetTexture("terrain.png"); + TransparentEffect.VertexColorEnabled = true; + + ChunkMeshes = new List(); + IncomingChunks = new ConcurrentBag(); + } + + void MeshCompleted(object sender, RendererEventArgs e) + { + IncomingChunks.Add(e.Result); + } + + void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case "Position": + var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D( + (int)Game.Client.Position.X, 0, (int)Game.Client.Position.Z)); + Game.PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter)); + break; + } + } + + public void Update(GameTime gameTime) + { + Mesh mesh; + while (IncomingChunks.TryTake(out mesh)) + { + ChunkMeshes.Add(mesh); + } + } + + private static readonly BlendState ColorWriteDisable = new BlendState() + { + ColorWriteChannels = ColorWriteChannels.None + }; + + public void Draw(GameTime gameTime) + { + Game.Camera.ApplyTo(OpaqueEffect); + Game.Camera.ApplyTo(TransparentEffect); + + int verticies = 0, chunks = 0; + Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default; + for (int i = 0; i < ChunkMeshes.Count; i++) + { + if (Game.Camera.Frustum.Intersects(ChunkMeshes[i].BoundingBox)) + { + verticies += ChunkMeshes[i].GetTotalVertices(); + chunks++; + ChunkMeshes[i].Draw(OpaqueEffect, 0); + } + } + + Game.GraphicsDevice.BlendState = ColorWriteDisable; + for (int i = 0; i < ChunkMeshes.Count; i++) + { + if (Game.Camera.Frustum.Intersects(ChunkMeshes[i].BoundingBox)) + { + if (!ChunkMeshes[i].IsDisposed) + verticies += ChunkMeshes[i].GetTotalVertices(); + ChunkMeshes[i].Draw(TransparentEffect, 1); + } + } + + Game.GraphicsDevice.BlendState = BlendState.NonPremultiplied; + for (int i = 0; i < ChunkMeshes.Count; i++) + { + if (Game.Camera.Frustum.Intersects(ChunkMeshes[i].BoundingBox)) + { + if (!ChunkMeshes[i].IsDisposed) + verticies += ChunkMeshes[i].GetTotalVertices(); + ChunkMeshes[i].Draw(TransparentEffect, 1); + } + } + } + } +} \ No newline at end of file diff --git a/TrueCraft.Client/Modules/HighlightModule.cs b/TrueCraft.Client/Modules/HighlightModule.cs new file mode 100644 index 0000000..d38523e --- /dev/null +++ b/TrueCraft.Client/Modules/HighlightModule.cs @@ -0,0 +1,95 @@ +using System; +using Microsoft.Xna.Framework.Graphics; +using TrueCraft.API; +using TrueCraft.Client.Rendering; +using Microsoft.Xna.Framework; + +namespace TrueCraft.Client.Modules +{ + public class HighlightModule : IGraphicalModule + { + public TrueCraftGame Game { get; set; } + + private Texture2D HighlightTexture { get; set; } + private Coordinates3D HighlightedBlock { get; set; } + private Mesh HighlightMesh { get; set; } + private BasicEffect HighlightEffect { get; set; } + + public HighlightModule(TrueCraftGame game) + { + Game = game; + + const int size = 64; + HighlightedBlock = -Coordinates3D.One; + HighlightTexture = new Texture2D(Game.GraphicsDevice, size, size); + + var colors = new Color[size * size]; + for (int i = 0; i < colors.Length; i++) + colors[i] = Color.Transparent; + for (int x = 0; x < size; x++) + colors[x] = Color.Black; // Top + for (int x = 0; x < size; x++) + colors[x + (size - 1) * size] = Color.Black; // Bottom + for (int y = 0; y < size; y++) + colors[y * size] = Color.Black; // Left + for (int y = 0; y < size; y++) + colors[y * size + (size - 1)] = Color.Black; // Right + + HighlightTexture.SetData(colors); + var texcoords = new[] + { + Vector2.UnitX + Vector2.UnitY, + Vector2.UnitY, + Vector2.Zero, + Vector2.UnitX + }; + int[] indicies; + var verticies = BlockRenderer.CreateUniformCube(Microsoft.Xna.Framework.Vector3.Zero, + texcoords, VisibleFaces.All, 0, out indicies, Color.White); + HighlightMesh = new Mesh(Game, verticies, indicies); + + HighlightEffect = new BasicEffect(Game.GraphicsDevice); + HighlightEffect.EnableDefaultLighting(); + HighlightEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3(); + HighlightEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3(); + HighlightEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3(); + HighlightEffect.TextureEnabled = true; + HighlightEffect.Texture = HighlightTexture; + HighlightEffect.VertexColorEnabled = true; + } + + public void Update(GameTime gameTime) + { + var direction = Microsoft.Xna.Framework.Vector3.Transform( + -Microsoft.Xna.Framework.Vector3.UnitZ, + Matrix.CreateRotationX(MathHelper.ToRadians(Game.Client.Pitch)) * + Matrix.CreateRotationY(MathHelper.ToRadians(Game.Client.Yaw))); + + var cast = VoxelCast.Cast(Game.Client.World, + new TrueCraft.API.Ray(Game.Camera.Position, + new TrueCraft.API.Vector3(direction.X, direction.Y, direction.Z)), + Game.BlockRepository, TrueCraftGame.Reach); + + if (cast == null) + HighlightedBlock = -Coordinates3D.One; + else + { + HighlightedBlock = cast.Item1; + HighlightEffect.World = Matrix.CreateScale(1.02f) * + Matrix.CreateTranslation(new Microsoft.Xna.Framework.Vector3( + cast.Item1.X, cast.Item1.Y, cast.Item1.Z)); + } + } + + public void Draw(GameTime gameTime) + { + Game.Camera.ApplyTo(HighlightEffect); + + if (HighlightedBlock != -Coordinates3D.One) + { + Game.GraphicsDevice.DepthStencilState = DepthStencilState.None; + HighlightMesh.Draw(HighlightEffect); + } + } + } +} diff --git a/TrueCraft.Client/Modules/IGameplayModule.cs b/TrueCraft.Client/Modules/IGameplayModule.cs new file mode 100644 index 0000000..01f551f --- /dev/null +++ b/TrueCraft.Client/Modules/IGameplayModule.cs @@ -0,0 +1,10 @@ +using System; +using Microsoft.Xna.Framework; + +namespace TrueCraft.Client.Modules +{ + public interface IGameplayModule + { + void Update(GameTime gameTime); + } +} \ No newline at end of file diff --git a/TrueCraft.Client/Modules/IGraphicalModule.cs b/TrueCraft.Client/Modules/IGraphicalModule.cs new file mode 100644 index 0000000..f08f119 --- /dev/null +++ b/TrueCraft.Client/Modules/IGraphicalModule.cs @@ -0,0 +1,10 @@ +using System; +using Microsoft.Xna.Framework; + +namespace TrueCraft.Client.Modules +{ + public interface IGraphicalModule : IGameplayModule + { + void Draw(GameTime gameTime); + } +} \ No newline at end of file diff --git a/TrueCraft.Client/Modules/IInputModule.cs b/TrueCraft.Client/Modules/IInputModule.cs new file mode 100644 index 0000000..edd1df7 --- /dev/null +++ b/TrueCraft.Client/Modules/IInputModule.cs @@ -0,0 +1,12 @@ +using Microsoft.Xna.Framework; +using TrueCraft.Client.Input; + +namespace TrueCraft.Client.Modules +{ + public interface IInputModule : IGameplayModule + { + bool KeyDown(GameTime gameTime, KeyboardKeyEventArgs e); + bool KeyUp(GameTime gameTime, KeyboardKeyEventArgs e); + void MouseMove(GameTime gameTime, MouseMoveEventArgs e); + } +} diff --git a/TrueCraft.Client/Modules/PlayerControlModule.cs b/TrueCraft.Client/Modules/PlayerControlModule.cs new file mode 100644 index 0000000..af9aea1 --- /dev/null +++ b/TrueCraft.Client/Modules/PlayerControlModule.cs @@ -0,0 +1,129 @@ +using System; +using System.Diagnostics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using TrueCraft.Client.Input; +using TCVector3 = TrueCraft.API.Vector3; + +namespace TrueCraft.Client.Modules +{ + public class PlayerControlModule : IInputModule + { + private TrueCraftGame Game { get; set; } + private Vector3 Delta { get; set; } + + public PlayerControlModule(TrueCraftGame game) + { + Game = game; + } + + public bool KeyDown(GameTime gameTime, KeyboardKeyEventArgs e) + { + switch (e.Key) + { + // Exit game + case Keys.Escape: + Process.GetCurrentProcess().Kill(); + return true; + + // Take a screenshot. + case Keys.F2: + Game.TakeScreenshot(); + return true; + + // Move to the left. + case Keys.A: + case Keys.Left: + Delta += Vector3.Left; + return true; + + // Move to the right. + case Keys.D: + case Keys.Right: + Delta += Vector3.Right; + return true; + + // Move forwards. + case Keys.W: + case Keys.Up: + Delta += Vector3.Forward; + return true; + + // Move backwards. + case Keys.S: + case Keys.Down: + Delta += Vector3.Backward; + return true; + + case Keys.Space: + if (Math.Floor(Game.Client.Position.Y) == Game.Client.Position.Y) + Game.Client.Velocity += TrueCraft.API.Vector3.Up * 0.3; + return true; + } + return false; + } + + public bool KeyUp(GameTime gameTime, KeyboardKeyEventArgs e) + { + switch (e.Key) + { + // Stop moving to the left. + case Keys.A: + case Keys.Left: + Delta -= Vector3.Left; + return true; + + // Stop moving to the right. + case Keys.D: + case Keys.Right: + Delta -= Vector3.Right; + return true; + + // Stop moving forwards. + case Keys.W: + case Keys.Up: + Delta -= Vector3.Forward; + return true; + + // Stop moving backwards. + case Keys.S: + case Keys.Down: + Delta -= Vector3.Backward; + return true; + } + return false; + } + + public void MouseMove(GameTime gameTime, MouseMoveEventArgs e) + { + var centerX = Game.GraphicsDevice.Viewport.Width / 2; + var centerY = Game.GraphicsDevice.Viewport.Height / 2; + Mouse.SetPosition(centerX, centerY); + + var look = new Vector2((centerX - e.X), (centerY - e.Y)) + * (float)(gameTime.ElapsedGameTime.TotalSeconds * 30); + + Game.Client.Yaw += look.X; + Game.Client.Pitch += look.Y; + Game.Client.Yaw %= 360; + Game.Client.Pitch = MathHelper.Clamp(Game.Client.Pitch, -89.9f, 89.9f); + } + + public void Update(GameTime gameTime) + { + if (Delta != Vector3.Zero) + { + var lookAt = Vector3.Transform(Delta, Matrix.CreateRotationY(MathHelper.ToRadians(Game.Client.Yaw))); + + lookAt.X *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717); + lookAt.Z *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717); + + Game.Bobbing += Math.Max(Math.Abs(lookAt.X), Math.Abs(lookAt.Z)); + + Game.Client.Velocity = new TCVector3(lookAt.X, Game.Client.Velocity.Y, lookAt.Z); + } + else + Game.Client.Velocity *= new TCVector3(0, 1, 0); + } + } +} diff --git a/TrueCraft.Client/Rendering/Camera.cs b/TrueCraft.Client/Rendering/Camera.cs index 0327c89..8a38708 100644 --- a/TrueCraft.Client/Rendering/Camera.cs +++ b/TrueCraft.Client/Rendering/Camera.cs @@ -133,18 +133,20 @@ namespace TrueCraft.Client.Rendering effectMatrices.View = _view; effectMatrices.Projection = _projection; - effectMatrices.World = Matrix.Identity; } /// /// Returns the bounding frustum calculated for this camera. /// /// - public BoundingFrustum GetFrustum() + public BoundingFrustum Frustum { - if (_isDirty) - Recalculate(); - return _frustum; + get + { + if (_isDirty) + Recalculate(); + return _frustum; + } } /// diff --git a/TrueCraft.Client/TrueCraft.Client.csproj b/TrueCraft.Client/TrueCraft.Client.csproj index d736895..d89d102 100644 --- a/TrueCraft.Client/TrueCraft.Client.csproj +++ b/TrueCraft.Client/TrueCraft.Client.csproj @@ -1,226 +1,237 @@ - - - - - Debug - AnyCPU - {A6516869-A2FB-4E31-85C8-2285490CB32C} - WinExe - TrueCraft.Client - TrueCraft.Client - False - False - False - OnBuildSuccess - False - False - False - bin\Release - Full - obj\$(Configuration)\ - 4 - v4.5 - - - $(DefineConstants);WINDOWS - False - False - obj\ - Project - - - $(DefineConstants);WINDOWS - true - - - 4194304 - AnyCPU - True - False - Auto - 4096 - - - x86 - - - localhost TestUser - - - true - - - - - - - - - - - true - bin\Optimized Debug - DEBUG; - true - - - false - - - - ..\lib\Ionic.Zip.Reduced.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {4488498D-976D-4DA3-BF72-109531AF0488} - fNbt - - - {FEE55B54-91B0-4325-A2C3-D576C0B7A81F} - TrueCraft.API - - - {FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D} - TrueCraft.Core - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + + + + Debug + AnyCPU + {A6516869-A2FB-4E31-85C8-2285490CB32C} + WinExe + TrueCraft.Client + TrueCraft.Client + False + False + False + OnBuildSuccess + False + False + False + bin\Release + Full + obj\$(Configuration)\ + 4 + v4.5 + + + $(DefineConstants);WINDOWS + False + False + obj\ + Project + + + $(DefineConstants);WINDOWS + true + + + 4194304 + AnyCPU + True + False + Auto + 4096 + + + x86 + + + localhost TestUser + + + true + + + + + + + + + + + true + bin\Optimized Debug + DEBUG; + true + + + false + + + + ..\lib\Ionic.Zip.Reduced.dll + + + + + ..\packages\MonoGame.Framework.Linux.3.4.0.459\lib\net40\MonoGame.Framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {4488498D-976D-4DA3-BF72-109531AF0488} + fNbt + + + {FEE55B54-91B0-4325-A2C3-D576C0B7A81F} + TrueCraft.API + + + {FA4BE9A3-DBF0-4380-BA2B-FFAA71C4706D} + TrueCraft.Core + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + \ No newline at end of file diff --git a/TrueCraft.Client/TrueCraftGame.cs b/TrueCraft.Client/TrueCraftGame.cs index 33135b9..d5d2d31 100644 --- a/TrueCraft.Client/TrueCraftGame.cs +++ b/TrueCraft.Client/TrueCraftGame.cs @@ -1,60 +1,58 @@ using System; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using Microsoft.Xna.Framework.Graphics; +using System.Collections.Concurrent; using System.Collections.Generic; -using TrueCraft.Client.Interface; +using System.ComponentModel; using System.IO; using System.Net; -using TrueCraft.API; -using TrueCraft.Client.Rendering; -using System.Linq; -using System.ComponentModel; -using TrueCraft.Core.Networking.Packets; -using TrueCraft.API.World; -using System.Collections.Concurrent; -using TrueCraft.Client.Input; -using TrueCraft.Core; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; using MonoGame.Utilities.Png; -using System.Diagnostics; -using TrueCraft.Core.Logic.Blocks; +using TrueCraft.API; +using TrueCraft.API.Logic; +using TrueCraft.API.World; +using TrueCraft.Core; +using TrueCraft.Core.Networking.Packets; +using TrueCraft.Client.Input; +using TrueCraft.Client.Interface; +using TrueCraft.Client.Modules; +using TrueCraft.Client.Rendering; namespace TrueCraft.Client { public class TrueCraftGame : Game { - private MultiplayerClient Client { get; set; } - private GraphicsDeviceManager Graphics { get; set; } - private List Interfaces { get; set; } - private FontRenderer Pixel { get; set; } + public MultiplayerClient Client { get; private set; } + public GraphicsDeviceManager Graphics { get; private set; } + public TextureMapper TextureMapper { get; private set; } + public Camera Camera { get; private set; } + public ConcurrentBag PendingMainThreadActions { get; set; } + public double Bobbing { get; set; } + + private List Modules { get; set; } private SpriteBatch SpriteBatch { get; set; } + private KeyboardHandler KeyboardComponent { get; set; } + private MouseHandler MouseComponent { get; set; } + private RenderTarget2D RenderTarget { get; set; } + + private FontRenderer Pixel { get; set; } private IPEndPoint EndPoint { get; set; } - private ChunkRenderer ChunkConverter { get; set; } private DateTime LastPhysicsUpdate { get; set; } private DateTime NextPhysicsUpdate { get; set; } - private List ChunkMeshes { get; set; } - public ConcurrentBag PendingMainThreadActions { get; set; } - private ConcurrentBag IncomingChunks { get; set; } - public ChatInterface ChatInterface { get; set; } - public DebugInterface DebugInterface { get; set; } - private RenderTarget2D RenderTarget; - private BoundingFrustum CameraView; - private Camera Camera; - private bool MouseCaptured; - private KeyboardComponent KeyboardComponent { get; set; } - private MouseComponent MouseComponent { get; set; } + private bool MouseCaptured { get; set; } private GameTime GameTime { get; set; } private Microsoft.Xna.Framework.Vector3 Delta { get; set; } - private TextureMapper TextureMapper { get; set; } - private double Bobbing { get; set; } - private Coordinates3D HighlightedBlock { get; set; } - private Mesh HighlightMesh { get; set; } - private Texture2D HighlightTexture { get; set; } - private BasicEffect OpaqueEffect, HighlightEffect; - private AlphaTestEffect TransparentEffect; public static readonly int Reach = 5; + public IBlockRepository BlockRepository + { + get + { + return Client.World.World.BlockRepository; + } + } + public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint) { Window.Title = "TrueCraft"; @@ -68,75 +66,46 @@ namespace TrueCraft.Client EndPoint = endPoint; LastPhysicsUpdate = DateTime.MinValue; NextPhysicsUpdate = DateTime.MinValue; - ChunkMeshes = new List(); - IncomingChunks = new ConcurrentBag(); PendingMainThreadActions = new ConcurrentBag(); MouseCaptured = true; Bobbing = 0; - var keyboardComponent = new KeyboardComponent(this); + var keyboardComponent = new KeyboardHandler(this); KeyboardComponent = keyboardComponent; Components.Add(keyboardComponent); - var mouseComponent = new MouseComponent(this); + var mouseComponent = new MouseHandler(this); MouseComponent = mouseComponent; Components.Add(mouseComponent); } protected override void Initialize() { - Interfaces = new List(); - SpriteBatch = new SpriteBatch(GraphicsDevice); + Modules = new List(); + base.Initialize(); // (calls LoadContent) - ChunkConverter = new ChunkRenderer(Client.World, this, Client.World.World.BlockRepository); - Client.ChunkLoaded += (sender, e) => ChunkConverter.Enqueue(e.Chunk); - //Client.ChunkModified += (sender, e) => ChunkConverter.Enqueue(e.Chunk, true); - ChunkConverter.MeshCompleted += ChunkConverter_MeshGenerated; - ChunkConverter.Start(); + + Modules.Add(new ChunkModule(this)); + Modules.Add(new HighlightModule(this)); + Modules.Add(new PlayerControlModule(this)); + Client.PropertyChanged += HandleClientPropertyChanged; Client.Connect(EndPoint); + var centerX = GraphicsDevice.Viewport.Width / 2; var centerY = GraphicsDevice.Viewport.Height / 2; Mouse.SetPosition(centerX, centerY); + Camera = new Camera(GraphicsDevice.Viewport.AspectRatio, 70.0f, 0.1f, 1000.0f); UpdateCamera(); - Window.ClientSizeChanged += (sender, e) => CreateRenderTarget(); + MouseComponent.Move += OnMouseComponentMove; KeyboardComponent.KeyDown += OnKeyboardKeyDown; KeyboardComponent.KeyUp += OnKeyboardKeyUp; + + Window.ClientSizeChanged += (sender, e) => CreateRenderTarget(); CreateRenderTarget(); - } - - private void InitializeHighlightedBlock() - { - const int size = 64; - HighlightedBlock = -Coordinates3D.One; - HighlightTexture = new Texture2D(GraphicsDevice, size, size); - - var colors = new Color[size * size]; - for (int i = 0; i < colors.Length; i++) - colors[i] = Color.Transparent; - for (int x = 0; x < size; x++) - colors[x] = Color.Black; // Top - for (int x = 0; x < size; x++) - colors[x + (size - 1) * size] = Color.Black; // Bottom - for (int y = 0; y < size; y++) - colors[y * size] = Color.Black; // Left - for (int y = 0; y < size; y++) - colors[y * size + (size - 1)] = Color.Black; // Right - - HighlightTexture.SetData(colors); - var texcoords = new[] - { - Vector2.UnitX + Vector2.UnitY, - Vector2.UnitY, - Vector2.Zero, - Vector2.UnitX - }; - int[] indicies; - var verticies = BlockRenderer.CreateUniformCube(Microsoft.Xna.Framework.Vector3.Zero, - texcoords, VisibleFaces.All, 0, out indicies, Color.White); - HighlightMesh = new Mesh(this, verticies, indicies); + SpriteBatch = new SpriteBatch(GraphicsDevice); } private void CreateRenderTarget() @@ -145,20 +114,12 @@ namespace TrueCraft.Client false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24); } - void ChunkConverter_MeshGenerated(object sender, RendererEventArgs e) - { - IncomingChunks.Add(e.Result); - } - void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "Position": UpdateCamera(); - var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D( - (int)Client.Position.X, 0, (int)Client.Position.Z)); - PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter)); break; } } @@ -174,212 +135,52 @@ namespace TrueCraft.Client TextureMapper.AddTexturePack(TexturePack.FromArchive(Path.Combine(TexturePack.TexturePackPath, UserSettings.Local.SelectedTexturePack))); Pixel = new FontRenderer( - new Font(Content, "Fonts/Pixel", FontStyle.Regular), + new Font(Content, "Fonts/Pixel"), new Font(Content, "Fonts/Pixel", FontStyle.Bold), null, // No support for underlined or strikethrough yet. The FontRenderer will revert to using the regular font style. null, // (I don't think BMFont has those options?) new Font(Content, "Fonts/Pixel", FontStyle.Italic)); - Interfaces.Add(ChatInterface = new ChatInterface(Client, KeyboardComponent, Pixel)); - Interfaces.Add(DebugInterface = new DebugInterface(Client, Pixel)); - - ChatInterface.IsVisible = true; - DebugInterface.IsVisible = true; - - OpaqueEffect = new BasicEffect(GraphicsDevice); - OpaqueEffect.EnableDefaultLighting(); - OpaqueEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3(); - OpaqueEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3(); - OpaqueEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3(); - OpaqueEffect.TextureEnabled = true; - OpaqueEffect.Texture = TextureMapper.GetTexture("terrain.png"); - OpaqueEffect.FogEnabled = true; - OpaqueEffect.FogStart = 512f; - OpaqueEffect.FogEnd = 1000f; - OpaqueEffect.FogColor = Color.CornflowerBlue.ToVector3(); - OpaqueEffect.VertexColorEnabled = true; - - TransparentEffect = new AlphaTestEffect(GraphicsDevice); - TransparentEffect.AlphaFunction = CompareFunction.Greater; - TransparentEffect.ReferenceAlpha = 127; - TransparentEffect.Texture = TextureMapper.GetTexture("terrain.png"); - TransparentEffect.VertexColorEnabled = true; - - InitializeHighlightedBlock(); - - HighlightEffect = new BasicEffect(GraphicsDevice); - HighlightEffect.EnableDefaultLighting(); - HighlightEffect.DirectionalLight0.SpecularColor = Color.Black.ToVector3(); - HighlightEffect.DirectionalLight1.SpecularColor = Color.Black.ToVector3(); - HighlightEffect.DirectionalLight2.SpecularColor = Color.Black.ToVector3(); - HighlightEffect.TextureEnabled = true; - HighlightEffect.Texture = HighlightTexture; - HighlightEffect.VertexColorEnabled = true; base.LoadContent(); } - protected override void OnExiting(object sender, EventArgs args) - { - ChunkConverter.Stop(); - base.OnExiting(sender, args); - } - private void OnKeyboardKeyDown(object sender, KeyboardKeyEventArgs e) { - // TODO: Rebindable keys - // TODO: Horizontal terrain collisions - - switch (e.Key) + foreach (var module in Modules) { - // Close game (or chat). - case Keys.Escape: - if (ChatInterface.HasFocus) - ChatInterface.HasFocus = false; - else - Process.GetCurrentProcess().Kill(); - break; - - // Open chat window. - case Keys.T: - if (!ChatInterface.HasFocus && ChatInterface.IsVisible) - ChatInterface.HasFocus = true; - break; - - // Open chat window. - case Keys.OemQuestion: - if (!ChatInterface.HasFocus && ChatInterface.IsVisible) - ChatInterface.HasFocus = true; - break; - - // Close chat window. - case Keys.Enter: - if (ChatInterface.HasFocus) - ChatInterface.HasFocus = false; - break; - - // Take a screenshot. - case Keys.F2: - TakeScreenshot(); - break; - - // Toggle debug view. - case Keys.F3: - DebugInterface.IsVisible = !DebugInterface.IsVisible; - break; - - // Change interface scale. - case Keys.F4: - foreach (var item in Interfaces) - { - item.Scale = (InterfaceScale)(item.Scale + 1); - if ((int)item.Scale > 2) - item.Scale = InterfaceScale.Small; - } - break; - - // Move to the left. - case Keys.A: - case Keys.Left: - if (ChatInterface.HasFocus) + var input = module as IInputModule; + if (input != null) + { + if (input.KeyDown(GameTime, e)) break; - Delta += Microsoft.Xna.Framework.Vector3.Left; - break; - - // Move to the right. - case Keys.D: - case Keys.Right: - if (ChatInterface.HasFocus) - break; - Delta += Microsoft.Xna.Framework.Vector3.Right; - break; - - // Move forwards. - case Keys.W: - case Keys.Up: - if (ChatInterface.HasFocus) - break; - Delta += Microsoft.Xna.Framework.Vector3.Forward; - break; - - // Move backwards. - case Keys.S: - case Keys.Down: - if (ChatInterface.HasFocus) - break; - Delta += Microsoft.Xna.Framework.Vector3.Backward; - break; - - case Keys.Space: - if (ChatInterface.HasFocus) - break; - if (Math.Floor(Client.Position.Y) == Client.Position.Y) // Crappy onground substitute - Client.Velocity += TrueCraft.API.Vector3.Up * 0.3; - break; - - // Toggle mouse capture. - case Keys.Tab: - MouseCaptured = !MouseCaptured; - break; + } } - } private void OnKeyboardKeyUp(object sender, KeyboardKeyEventArgs e) { - switch (e.Key) + foreach (var module in Modules) { - // Stop moving to the left. - case Keys.A: - case Keys.Left: - if (ChatInterface.HasFocus) break; - Delta -= Microsoft.Xna.Framework.Vector3.Left; - break; - - // Stop moving to the right. - case Keys.D: - case Keys.Right: - if (ChatInterface.HasFocus) break; - Delta -= Microsoft.Xna.Framework.Vector3.Right; - break; - - // Stop moving forwards. - case Keys.W: - case Keys.Up: - if (ChatInterface.HasFocus) break; - Delta -= Microsoft.Xna.Framework.Vector3.Forward; - break; - - // Stop moving backwards. - case Keys.S: - case Keys.Down: - if (ChatInterface.HasFocus) break; - Delta -= Microsoft.Xna.Framework.Vector3.Backward; - break; + var input = module as IInputModule; + if (input != null) + { + if (input.KeyUp(GameTime, e)) + break; + } } } private void OnMouseComponentMove(object sender, MouseMoveEventArgs e) { - if (MouseCaptured && IsActive) + foreach (var module in Modules) { - var centerX = GraphicsDevice.Viewport.Width / 2; - var centerY = GraphicsDevice.Viewport.Height / 2; - Mouse.SetPosition(centerX, centerY); - - var look = new Vector2((centerX - e.X), (centerY - e.Y)) - * (float)(GameTime.ElapsedGameTime.TotalSeconds * 30); - - Client.Yaw += look.X; - Client.Pitch += look.Y; - Client.Yaw %= 360; - Client.Pitch = Microsoft.Xna.Framework.MathHelper.Clamp(Client.Pitch, -89.9f, 89.9f); - - if (look != Vector2.Zero) - UpdateCamera(); + var input = module as IInputModule; + if (input != null) + input.MouseMove(GameTime, e); } } - private void TakeScreenshot() + public void TakeScreenshot() { var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".truecraft", "screenshots", DateTime.Now.ToString("yyyy-MM-dd_H.mm.ss") + ".png"); @@ -387,29 +188,19 @@ namespace TrueCraft.Client Directory.CreateDirectory(Path.GetDirectoryName(path)); using (var stream = File.OpenWrite(path)) new PngWriter().Write(RenderTarget, stream); - ChatInterface.AddMessage(string.Format("Screenshot saved as {0}", Path.GetFileName(path))); } protected override void Update(GameTime gameTime) { GameTime = gameTime; - foreach (var i in Interfaces) - { - i.Update(gameTime); - } - - Mesh mesh; - while (IncomingChunks.TryTake(out mesh)) - { - ChunkMeshes.Add(mesh); - } Action action; if (PendingMainThreadActions.TryTake(out action)) action(); IChunk chunk; - var adjusted = Client.World.World.FindBlockPosition(new Coordinates3D((int)Client.Position.X, 0, (int)Client.Position.Z), out chunk); + var adjusted = Client.World.World.FindBlockPosition( + new Coordinates3D((int)Client.Position.X, 0, (int)Client.Position.Z), out chunk); if (chunk != null && Client.LoggedIn) { if (chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) != 0) @@ -426,70 +217,30 @@ namespace TrueCraft.Client NextPhysicsUpdate = DateTime.UtcNow.AddMilliseconds(1000 / 20); } - if (Delta != Microsoft.Xna.Framework.Vector3.Zero) - { - var lookAt = Microsoft.Xna.Framework.Vector3.Transform( - Delta, Matrix.CreateRotationY(Microsoft.Xna.Framework.MathHelper.ToRadians(Client.Yaw))); - - lookAt.X *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717); - lookAt.Z *= (float)(gameTime.ElapsedGameTime.TotalSeconds * 4.3717); - - Bobbing += Math.Max(Math.Abs(lookAt.X), Math.Abs(lookAt.Z)); - - Client.Velocity = new TrueCraft.API.Vector3(lookAt.X, Client.Velocity.Y, lookAt.Z); - } - else - Client.Velocity *= new TrueCraft.API.Vector3(0, 1, 0); + foreach (var module in Modules) + module.Update(gameTime); UpdateCamera(); + base.Update(gameTime); } private void UpdateCamera() { const double bobbingMultiplier = 0.015; + var bobbing = Bobbing * 1.5; + var xbob = Math.Cos(bobbing + Math.PI / 2) * bobbingMultiplier; + var ybob = Math.Sin(Math.PI / 2 - (2 * bobbing)) * bobbingMultiplier; Camera.Position = new TrueCraft.API.Vector3( - Client.Position.X + Math.Cos(bobbing + Math.PI / 2) * bobbingMultiplier - - (Client.Size.Width / 2), - Client.Position.Y + (Client.Size.Height - 0.5) - + Math.Sin(Math.PI / 2 - (2 * bobbing)) * bobbingMultiplier, + Client.Position.X + xbob - (Client.Size.Width / 2), + Client.Position.Y + (Client.Size.Height - 0.5) + ybob, Client.Position.Z - (Client.Size.Depth / 2)); Camera.Pitch = Client.Pitch; Camera.Yaw = Client.Yaw; - - CameraView = Camera.GetFrustum(); - - Camera.ApplyTo(HighlightEffect); - Camera.ApplyTo(OpaqueEffect); - Camera.ApplyTo(TransparentEffect); - - var direction = Microsoft.Xna.Framework.Vector3.Transform( - -Microsoft.Xna.Framework.Vector3.UnitZ, - Matrix.CreateRotationX(Microsoft.Xna.Framework.MathHelper.ToRadians(Client.Pitch)) * - Matrix.CreateRotationY(Microsoft.Xna.Framework.MathHelper.ToRadians(Client.Yaw))); - - var cast = VoxelCast.Cast(Client.World, - new TrueCraft.API.Ray(Camera.Position, new TrueCraft.API.Vector3( - direction.X, direction.Y, direction.Z)), - Client.World.World.BlockRepository, Reach); - - if (cast == null) - HighlightedBlock = -Coordinates3D.One; - else - { - HighlightEffect.World = Matrix.CreateScale(1.02f) * - Matrix.CreateTranslation(new Microsoft.Xna.Framework.Vector3( - cast.Item1.X, cast.Item1.Y, cast.Item1.Z)); - } } - private static readonly BlendState ColorWriteDisable = new BlendState() - { - ColorWriteChannels = ColorWriteChannels.None - }; - protected override void Draw(GameTime gameTime) { GraphicsDevice.SetRenderTarget(RenderTarget); @@ -499,55 +250,15 @@ namespace TrueCraft.Client GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp; GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; - int verticies = 0, chunks = 0; - GraphicsDevice.DepthStencilState = DepthStencilState.Default; - for (int i = 0; i < ChunkMeshes.Count; i++) + foreach (var module in Modules) { - if (CameraView.Intersects(ChunkMeshes[i].BoundingBox)) - { - verticies += ChunkMeshes[i].GetTotalVertices(); - chunks++; - ChunkMeshes[i].Draw(OpaqueEffect, 0); - } + var drawable = module as IGraphicalModule; + if (drawable != null) + drawable.Draw(gameTime); } - HighlightMesh.Draw(HighlightEffect); - - GraphicsDevice.BlendState = ColorWriteDisable; - for (int i = 0; i < ChunkMeshes.Count; i++) - { - if (CameraView.Intersects(ChunkMeshes[i].BoundingBox)) - { - if (!ChunkMeshes[i].IsDisposed) - verticies += ChunkMeshes[i].GetTotalVertices(); - ChunkMeshes[i].Draw(TransparentEffect, 1); - } - } - - GraphicsDevice.BlendState = BlendState.NonPremultiplied; - for (int i = 0; i < ChunkMeshes.Count; i++) - { - if (CameraView.Intersects(ChunkMeshes[i].BoundingBox)) - { - if (!ChunkMeshes[i].IsDisposed) - verticies += ChunkMeshes[i].GetTotalVertices(); - ChunkMeshes[i].Draw(TransparentEffect, 1); - } - } - - DebugInterface.Vertices = verticies; - DebugInterface.Chunks = chunks; - DebugInterface.HighlightedBlock = HighlightedBlock; - - HighlightMesh.Draw(HighlightEffect); - - SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, null); - for (int i = 0; i < Interfaces.Count; i++) - Interfaces[i].DrawSprites(gameTime, SpriteBatch); - SpriteBatch.End(); GraphicsDevice.SetRenderTarget(null); - GraphicsDevice.Clear(Color.CornflowerBlue); SpriteBatch.Begin(); SpriteBatch.Draw(RenderTarget, new Vector2(0)); SpriteBatch.End(); @@ -559,8 +270,6 @@ namespace TrueCraft.Client { if (disposing) { - ChunkConverter.Dispose(); - KeyboardComponent.Dispose(); MouseComponent.Dispose(); }