From 3da7d25a3db1f583308a81b81b68f09bbcaf76c4 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Wed, 7 Oct 2015 08:36:43 -0400 Subject: [PATCH] Rewrite chat interface --- TrueCraft.Client/Modules/ChatModule.cs | 216 +++++++++++++++++++++ TrueCraft.Client/Rendering/FontRenderer.cs | 6 +- TrueCraft.Client/TrueCraft.Client.csproj | 1 + TrueCraft.Client/TrueCraftGame.cs | 3 + TrueCraft.Core/World/Region.cs | 5 +- 5 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 TrueCraft.Client/Modules/ChatModule.cs diff --git a/TrueCraft.Client/Modules/ChatModule.cs b/TrueCraft.Client/Modules/ChatModule.cs new file mode 100644 index 0000000..67eb77a --- /dev/null +++ b/TrueCraft.Client/Modules/ChatModule.cs @@ -0,0 +1,216 @@ +using System; +using TrueCraft.Client.Input; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using TrueCraft.Client.Rendering; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework.Input; + +namespace TrueCraft.Client.Modules +{ + public class ChatModule : InputModule, IGraphicalModule + { + private static readonly int TimeToFade = 9 * 1000; + private static readonly int TimeToExpire = 10 * 1000; + private static readonly int MaxLines = 10; + + private struct Message + { + public string Text { get; set; } + public long Arrived { get; set; } + } + + private TrueCraftGame Game { get; set; } + private Texture2D Background { get; set; } + private FontRenderer Font { get; set; } + private SpriteBatch SpriteBatch { get; set; } + private List Messages { get; set; } + private Stopwatch Watch { get; set; } + private bool Editing { get; set; } + private string Text { get; set; } + + public ChatModule(TrueCraftGame game, FontRenderer font) + { + Game = game; + Font = font; + Messages = new List(); + Background = new Texture2D(Game.GraphicsDevice, 1, 1); + Background.SetData(new[] { new Color(Color.Black, 160) }); + SpriteBatch = new SpriteBatch(Game.GraphicsDevice); + Watch = new Stopwatch(); + Watch.Start(); + Text = string.Empty; + Game.Client.ChatMessage += (sender, e) => AddMessage(e.Message); + } + + public override bool KeyDown(GameTime gameTime, KeyboardKeyEventArgs e) + { + if (!Editing) + { + if (e.Key == Keys.T) + { + Editing = true; + return true; + } + return false; + } + if (e.Key == Keys.Back) + Text = Text.Length > 0 ? Text.Substring(0, Text.Length - 1) : Text; + else if (e.Key == Keys.Escape) + { + Editing = false; + Text = string.Empty; + } + else if (e.Key == Keys.Enter) + { + Game.Client.SendMessage(Text); + Editing = false; + Text = string.Empty; + } + else + { + var shift = (Keyboard.GetState().IsKeyDown(Keys.LeftShift) || Keyboard.GetState().IsKeyDown(Keys.RightShift)); + var value = default(char); + + if (TryParseKey(e.Key, shift, out value)) + Text += value; + } + return true; + } + + public override bool KeyUp(GameTime gameTime, KeyboardKeyEventArgs e) + { + return Editing; + } + + public void AddMessage(string text) + { + Console.WriteLine(text); + Messages.Insert(0, new Message { Text = text, Arrived = Watch.ElapsedMilliseconds }); + } + + public void Draw(GameTime gameTime) + { + SpriteBatch.Begin(); + const int height = 25; + int x = 5, y = (int)(Game.GraphicsDevice.Viewport.Height - Scale(22) * 2); + int width = Game.GraphicsDevice.Viewport.Width / 2; + int max = MaxLines; + if (Editing) + max = Game.GraphicsDevice.Viewport.Height / height; + for (int i = 0; i < Messages.Count && i < max; i++) + { + var time = Watch.ElapsedMilliseconds - Messages[i].Arrived; + if (time >= TimeToExpire && !Editing) + continue; + byte alpha = 255; + if (time > TimeToFade && !Editing) + { + var t = TimeToExpire - time / (double)(TimeToExpire - TimeToFade); + alpha = (byte)(t * 256); + } + SpriteBatch.Draw(Background, new Rectangle( + x, y - (i * height), width, height), new Color(Color.White, alpha)); + Font.DrawText(SpriteBatch, x, y - (i * height) - 5, Messages[i].Text, alpha: alpha); + } + if (Editing) + { + SpriteBatch.Draw(Background, + new Rectangle(0, Game.GraphicsDevice.Viewport.Height - height, + Game.GraphicsDevice.Viewport.Width, height), Color.White); + if (Watch.Elapsed.Seconds % 2 == 0) + Font.DrawText(SpriteBatch, 3, Game.GraphicsDevice.Viewport.Height - height - 5, Text); + else + Font.DrawText(SpriteBatch, 3, Game.GraphicsDevice.Viewport.Height - height - 5, Text + "_"); + } + SpriteBatch.End(); + } + + public override void Update(GameTime gameTime) + { + } + + private float Scale(float value) + { + return value * Game.ScaleFactor * 2; + } + + private static bool TryParseKey(Keys key, bool shift, out char value) + { + // Credit to Roy Triesscheijn for thinking of this. + // His implementation of this solution can be found at: + // http://roy-t.nl/index.php/2010/02/11/code-snippet-converting-keyboard-input-to-text-in-xna/ + + switch (key) + { + case Keys.NumPad1: value = '1'; break; + case Keys.NumPad2: value = '2'; break; + case Keys.NumPad3: value = '3'; break; + case Keys.NumPad4: value = '4'; break; + case Keys.NumPad5: value = '5'; break; + case Keys.NumPad6: value = '6'; break; + case Keys.NumPad7: value = '7'; break; + case Keys.NumPad8: value = '8'; break; + case Keys.NumPad9: value = '9'; break; + case Keys.NumPad0: value = '0'; break; + + case Keys.D1: value = (shift) ? '!' : '1'; break; + case Keys.D2: value = (shift) ? '@' : '2'; break; + case Keys.D3: value = (shift) ? '#' : '3'; break; + case Keys.D4: value = (shift) ? '$' : '4'; break; + case Keys.D5: value = (shift) ? '%' : '5'; break; + case Keys.D6: value = (shift) ? '^' : '6'; break; + case Keys.D7: value = (shift) ? '&' : '7'; break; + case Keys.D8: value = (shift) ? '*' : '8'; break; + case Keys.D9: value = (shift) ? '(' : '9'; break; + case Keys.D0: value = (shift) ? ')' : '0'; break; + + case Keys.A: value = (shift) ? 'A' : 'a'; break; + case Keys.B: value = (shift) ? 'B' : 'b'; break; + case Keys.C: value = (shift) ? 'C' : 'c'; break; + case Keys.D: value = (shift) ? 'D' : 'd'; break; + case Keys.E: value = (shift) ? 'E' : 'e'; break; + case Keys.F: value = (shift) ? 'F' : 'f'; break; + case Keys.G: value = (shift) ? 'G' : 'g'; break; + case Keys.H: value = (shift) ? 'H' : 'h'; break; + case Keys.I: value = (shift) ? 'I' : 'i'; break; + case Keys.J: value = (shift) ? 'J' : 'j'; break; + case Keys.K: value = (shift) ? 'K' : 'k'; break; + case Keys.L: value = (shift) ? 'L' : 'l'; break; + case Keys.M: value = (shift) ? 'M' : 'm'; break; + case Keys.N: value = (shift) ? 'N' : 'n'; break; + case Keys.O: value = (shift) ? 'O' : 'o'; break; + case Keys.P: value = (shift) ? 'P' : 'p'; break; + case Keys.Q: value = (shift) ? 'Q' : 'q'; break; + case Keys.R: value = (shift) ? 'R' : 'r'; break; + case Keys.S: value = (shift) ? 'S' : 's'; break; + case Keys.T: value = (shift) ? 'T' : 't'; break; + case Keys.U: value = (shift) ? 'U' : 'u'; break; + case Keys.V: value = (shift) ? 'V' : 'v'; break; + case Keys.W: value = (shift) ? 'W' : 'w'; break; + case Keys.X: value = (shift) ? 'X' : 'x'; break; + case Keys.Y: value = (shift) ? 'Y' : 'y'; break; + case Keys.Z: value = (shift) ? 'Z' : 'z'; break; + + case Keys.OemTilde: value = (shift) ? '~' : '`'; break; + case Keys.OemSemicolon: value = (shift) ? ':' : ';'; break; + case Keys.OemQuotes: value = (shift) ? '"' : '\''; break; + case Keys.OemQuestion: value = (shift) ? '?' : '/'; break; + case Keys.OemPlus: value = (shift) ? '+' : '='; break; + case Keys.OemPipe: value = (shift) ? '|' : '\\'; break; + case Keys.OemPeriod: value = (shift) ? '>' : '.'; break; + case Keys.OemOpenBrackets: value = (shift) ? '{' : '['; break; + case Keys.OemCloseBrackets: value = (shift) ? '}' : ']'; break; + case Keys.OemMinus: value = (shift) ? '_' : '-'; break; + case Keys.OemComma: value = (shift) ? '<' : ','; break; + + case Keys.Space: value = ' '; break; + + default: value = default(char); return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/TrueCraft.Client/Rendering/FontRenderer.cs b/TrueCraft.Client/Rendering/FontRenderer.cs index fe56a90..730216f 100644 --- a/TrueCraft.Client/Rendering/FontRenderer.cs +++ b/TrueCraft.Client/Rendering/FontRenderer.cs @@ -61,7 +61,7 @@ namespace TrueCraft.Client.Rendering /// /// /// - public void DrawText(SpriteBatch spriteBatch, int x, int y, string text, float scale = 1.0f) + public void DrawText(SpriteBatch spriteBatch, int x, int y, string text, float scale = 1.0f, byte alpha = 255) { var dx = x; var dy = y; @@ -96,8 +96,8 @@ namespace TrueCraft.Client.Rendering (int)(glyph.Width * scale), (int)(glyph.Height * scale)); - spriteBatch.Draw(font.GetTexture(glyph.Page), shadowRectangle, sourceRectangle, new Color(63, 63, 21)); - spriteBatch.Draw(font.GetTexture(glyph.Page), destRectangle, sourceRectangle, color); + spriteBatch.Draw(font.GetTexture(glyph.Page), shadowRectangle, sourceRectangle, new Color(63, 63, 21, alpha)); + spriteBatch.Draw(font.GetTexture(glyph.Page), destRectangle, sourceRectangle, new Color(color, alpha)); dx += (int)(glyph.XAdvance * scale); } } diff --git a/TrueCraft.Client/TrueCraft.Client.csproj b/TrueCraft.Client/TrueCraft.Client.csproj index 368ed78..fcc9163 100644 --- a/TrueCraft.Client/TrueCraft.Client.csproj +++ b/TrueCraft.Client/TrueCraft.Client.csproj @@ -145,6 +145,7 @@ + diff --git a/TrueCraft.Client/TrueCraftGame.cs b/TrueCraft.Client/TrueCraftGame.cs index 948ab4f..9e8c644 100644 --- a/TrueCraft.Client/TrueCraftGame.cs +++ b/TrueCraft.Client/TrueCraftGame.cs @@ -32,6 +32,7 @@ namespace TrueCraft.Client public ConcurrentBag PendingMainThreadActions { get; set; } public double Bobbing { get; set; } public ChunkModule ChunkModule { get; set; } + public ChatModule ChatModule { get; set; } public float ScaleFactor { get; set; } public Coordinates3D HighlightedBlock { get; set; } public BlockFace HighlightedBlockFace { get; set; } @@ -121,9 +122,11 @@ namespace TrueCraft.Client ChunkModule = new ChunkModule(this); DebugInfoModule = new DebugInfoModule(this, Pixel); + ChatModule = new ChatModule(this, Pixel); Modules.Add(ChunkModule); Modules.Add(new HighlightModule(this)); + Modules.Add(ChatModule); Modules.Add(new PlayerControlModule(this)); Modules.Add(new HUDModule(this, Pixel)); Modules.Add(DebugInfoModule); diff --git a/TrueCraft.Core/World/Region.cs b/TrueCraft.Core/World/Region.cs index 58d3dbc..1a9b8f7 100644 --- a/TrueCraft.Core/World/Region.cs +++ b/TrueCraft.Core/World/Region.cs @@ -32,6 +32,7 @@ namespace TrueCraft.Core.World public World World { get; set; } private Stream regionFile { get; set; } + private object streamLock = new object(); /// /// Creates a new Region for server-side use at the given position using @@ -163,7 +164,7 @@ namespace TrueCraft.Core.World { lock (Chunks) { - lock (regionFile) + lock (streamLock) { var toRemove = new List(); foreach (var kvp in Chunks) @@ -262,7 +263,7 @@ namespace TrueCraft.Core.World { if (regionFile == null) return; - lock (regionFile) + lock (streamLock) { regionFile.Flush(); regionFile.Close();