TrueCraft/TrueCraft.Client/TrueCraftGame.cs

572 lines
23 KiB
C#
Raw Normal View History

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using TrueCraft.Client.Interface;
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;
2015-06-13 19:17:06 -04:00
using TrueCraft.Client.Input;
using TrueCraft.Core;
2015-06-23 15:56:35 -06:00
using MonoGame.Utilities.Png;
2015-09-20 18:11:02 -04:00
using System.Diagnostics;
using TrueCraft.Core.Logic.Blocks;
namespace TrueCraft.Client
{
public class TrueCraftGame : Game
{
private MultiplayerClient Client { get; set; }
private GraphicsDeviceManager Graphics { get; set; }
private List<IGameInterface> Interfaces { get; set; }
private FontRenderer Pixel { get; set; }
private SpriteBatch SpriteBatch { get; set; }
private IPEndPoint EndPoint { get; set; }
private ChunkRenderer ChunkConverter { get; set; }
private DateTime LastPhysicsUpdate { get; set; }
private DateTime NextPhysicsUpdate { get; set; }
private List<Mesh> ChunkMeshes { get; set; }
public ConcurrentBag<Action> PendingMainThreadActions { get; set; }
private ConcurrentBag<Mesh> IncomingChunks { get; set; }
public ChatInterface ChatInterface { get; set; }
public DebugInterface DebugInterface { get; set; }
private RenderTarget2D RenderTarget;
private BoundingFrustum CameraView;
2015-06-12 17:10:28 -04:00
private Camera Camera;
private bool MouseCaptured;
private KeyboardComponent KeyboardComponent { get; set; }
2015-06-13 19:17:06 -04:00
private MouseComponent MouseComponent { get; set; }
private GameTime GameTime { get; set; }
private Microsoft.Xna.Framework.Vector3 Delta { get; set; }
private TextureMapper TextureMapper { get; set; }
2015-09-20 18:11:02 -04:00
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 TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
{
Window.Title = "TrueCraft";
Content.RootDirectory = "Content";
Graphics = new GraphicsDeviceManager(this);
Graphics.SynchronizeWithVerticalRetrace = false;
Graphics.IsFullScreen = UserSettings.Local.IsFullscreen;
Graphics.PreferredBackBufferWidth = UserSettings.Local.WindowResolution.Width;
Graphics.PreferredBackBufferHeight = UserSettings.Local.WindowResolution.Height;
Client = client;
EndPoint = endPoint;
LastPhysicsUpdate = DateTime.MinValue;
NextPhysicsUpdate = DateTime.MinValue;
ChunkMeshes = new List<Mesh>();
IncomingChunks = new ConcurrentBag<Mesh>();
PendingMainThreadActions = new ConcurrentBag<Action>();
MouseCaptured = true;
2015-09-20 18:11:02 -04:00
Bobbing = 0;
2015-06-13 19:17:06 -04:00
var keyboardComponent = new KeyboardComponent(this);
KeyboardComponent = keyboardComponent;
Components.Add(keyboardComponent);
2015-06-13 19:17:06 -04:00
var mouseComponent = new MouseComponent(this);
MouseComponent = mouseComponent;
Components.Add(mouseComponent);
}
protected override void Initialize()
{
Interfaces = new List<IGameInterface>();
SpriteBatch = new SpriteBatch(GraphicsDevice);
base.Initialize(); // (calls LoadContent)
ChunkConverter = new ChunkRenderer(Client.World, this, Client.World.World.BlockRepository);
Client.ChunkLoaded += (sender, e) => ChunkConverter.Enqueue(e.Chunk);
2015-06-20 13:56:57 -04:00
//Client.ChunkModified += (sender, e) => ChunkConverter.Enqueue(e.Chunk, true);
ChunkConverter.MeshCompleted += ChunkConverter_MeshGenerated;
2015-05-29 15:46:44 -06:00
ChunkConverter.Start();
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);
2015-06-12 17:10:28 -04:00
UpdateCamera();
Window.ClientSizeChanged += (sender, e) => CreateRenderTarget();
2015-06-13 19:17:06 -04:00
MouseComponent.Move += OnMouseComponentMove;
KeyboardComponent.KeyDown += OnKeyboardKeyDown;
KeyboardComponent.KeyUp += OnKeyboardKeyUp;
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<Color>(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);
}
private void CreateRenderTarget()
{
RenderTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height,
false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
}
void ChunkConverter_MeshGenerated(object sender, RendererEventArgs<ReadOnlyChunk> e)
2015-05-29 15:46:44 -06:00
{
IncomingChunks.Add(e.Result);
2015-05-29 15:46:44 -06:00
}
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Position":
2015-06-12 17:10:28 -04:00
UpdateCamera();
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
(int)Client.Position.X, 0, (int)Client.Position.Z));
PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter));
break;
}
}
protected override void LoadContent()
{
// Ensure we have default textures loaded.
TextureMapper.LoadDefaults(GraphicsDevice);
// Load any custom textures if needed.
2015-06-17 09:10:42 -04:00
TextureMapper = new TextureMapper(GraphicsDevice);
if (UserSettings.Local.SelectedTexturePack != TexturePack.Default.Name)
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", 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)
{
// Close game (or chat).
case Keys.Escape:
if (ChatInterface.HasFocus)
ChatInterface.HasFocus = false;
else
2015-09-20 18:11:02 -04:00
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)
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)
{
// 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;
}
2015-06-13 19:17:06 -04:00
}
private void OnMouseComponentMove(object sender, MouseMoveEventArgs e)
{
if (MouseCaptured && IsActive)
{
var centerX = GraphicsDevice.Viewport.Width / 2;
var centerY = GraphicsDevice.Viewport.Height / 2;
Mouse.SetPosition(centerX, centerY);
2015-06-13 19:17:06 -04:00
2015-06-13 19:35:51 -04:00
var look = new Vector2((centerX - e.X), (centerY - e.Y))
* (float)(GameTime.ElapsedGameTime.TotalSeconds * 30);
2015-06-13 19:17:06 -04:00
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)
2015-06-12 17:10:28 -04:00
UpdateCamera();
}
}
private void TakeScreenshot()
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
2015-06-23 15:56:35 -06:00
".truecraft", "screenshots", DateTime.Now.ToString("yyyy-MM-dd_H.mm.ss") + ".png");
if (!Directory.Exists(Path.GetDirectoryName(path)))
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = File.OpenWrite(path))
2015-06-23 15:56:35 -06:00
new PngWriter().Write(RenderTarget, stream);
ChatInterface.AddMessage(string.Format("Screenshot saved as {0}", Path.GetFileName(path)));
}
protected override void Update(GameTime gameTime)
{
2015-06-13 19:17:06 -04:00
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);
2015-09-20 17:55:57 -04:00
if (chunk != null && Client.LoggedIn)
{
if (chunk.GetHeight((byte)adjusted.X, (byte)adjusted.Z) != 0)
Client.Physics.Update(gameTime.ElapsedGameTime);
}
if (NextPhysicsUpdate < DateTime.UtcNow && Client.LoggedIn)
{
// NOTE: This is to make the vanilla server send us chunk packets
// We should eventually make some means of detecing that we're on a vanilla server to enable this
// It's a waste of bandwidth to do it on a TrueCraft server
Client.QueuePacket(new PlayerGroundedPacket { OnGround = true });
Client.QueuePacket(new PlayerPositionAndLookPacket(Client.Position.X, Client.Position.Y,
Client.Position.Y + MultiplayerClient.Height, Client.Position.Z, Client.Yaw, Client.Pitch, false));
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);
2015-09-20 18:11:02 -04:00
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);
UpdateCamera();
base.Update(gameTime);
}
2015-06-12 17:10:28 -04:00
private void UpdateCamera()
{
2015-09-22 08:44:52 -04:00
const double bobbingMultiplier = 0.015;
var bobbing = Bobbing * 1.5;
2015-06-12 17:10:28 -04:00
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.Z - (Client.Size.Depth / 2));
2015-06-12 17:10:28 -04:00
Camera.Pitch = Client.Pitch;
Camera.Yaw = Client.Yaw;
2015-06-12 17:10:28 -04:00
CameraView = Camera.GetFrustum();
Camera.ApplyTo(HighlightEffect);
2015-06-12 17:10:28 -04:00
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()
{
2015-06-18 17:51:47 -04:00
ColorWriteChannels = ColorWriteChannels.None
};
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.SetRenderTarget(RenderTarget);
GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
Graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
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++)
{
2015-06-16 15:53:28 -04:00
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
{
2015-06-16 15:53:28 -04:00
verticies += ChunkMeshes[i].GetTotalVertices();
chunks++;
ChunkMeshes[i].Draw(OpaqueEffect, 0);
}
}
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();
base.Draw(gameTime);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
2015-06-18 21:55:35 -04:00
ChunkConverter.Dispose();
KeyboardComponent.Dispose();
MouseComponent.Dispose();
}
base.Dispose(disposing);
}
}
2015-05-29 17:43:23 -06:00
}