diff --git a/ClassicalSharp/Entities/Model/BlockModel.cs b/ClassicalSharp/Entities/Model/BlockModel.cs index c458d663c..fa26bb1c4 100644 --- a/ClassicalSharp/Entities/Model/BlockModel.cs +++ b/ClassicalSharp/Entities/Model/BlockModel.cs @@ -1,232 +1,231 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; -using ClassicalSharp.Entities; -using ClassicalSharp.GraphicsAPI; -using ClassicalSharp.Physics; -using ClassicalSharp.Renderers; -using ClassicalSharp.Textures; -using OpenTK; - -#if USE16_BIT -using BlockID = System.UInt16; -#else -using BlockID = System.Byte; -#endif - -namespace ClassicalSharp.Model { - - public class BlockModel : IModel { - - BlockID block = Block.Air; - float height; - TerrainAtlas1D atlas; - bool bright; - Vector3 minBB, maxBB; - public bool SwitchOrder = false; - ModelCache cache; - - public BlockModel(Game game) : base(game) { - cache = game.ModelCache; - Bobbing = false; - UsesSkin = false; - } - - public override void CreateParts() { } - - public override float NameYOffset { get { return height + 0.075f; } } - - public override float GetEyeY(Entity entity) { - BlockID block = Byte.Parse(entity.ModelName); - float minY = game.BlockInfo.MinBB[block].Y; - float maxY = game.BlockInfo.MaxBB[block].Y; - return block == 0 ? 1 : (minY + maxY) / 2; - } - - static Vector3 colShrink = new Vector3(0.75f/16, 0.75f/16, 0.75f/16); - public override Vector3 CollisionSize { - get { return (maxBB - minBB) - colShrink; } // to fit slightly inside - } - - static Vector3 offset = new Vector3(-0.5f, 0.0f, -0.5f); - public override AABB PickingBounds { - get { return new AABB(minBB, maxBB).Offset(offset); } - } - - public void CalcState(BlockID block) { - if (game.BlockInfo.Draw[block] == DrawType.Gas) { - minBB = Vector3.Zero; - maxBB = Vector3.One; - height = 1; - } else { - minBB = game.BlockInfo.MinBB[block]; - maxBB = game.BlockInfo.MaxBB[block]; - height = maxBB.Y - minBB.Y; - if (game.BlockInfo.Draw[block] == DrawType.Sprite) - height = 1; - } - } - - public override float RenderDistance(Entity p) { - block = Utils.FastByte(p.ModelName); - CalcState(block); - return base.RenderDistance(p); - } - - int lastTexId = -1; - public override void DrawModel(Entity p) { - // TODO: using 'is' is ugly, but means we can avoid creating - // a string every single time held block changes. - if (p is FakePlayer) { - block = ((FakePlayer)p).Block; - } else { - block = Utils.FastByte(p.ModelName); - } - - CalcState(block); - if (game.BlockInfo.FullBright[block]) { - for (int i = 0; i < cols.Length; i++) - cols[i] = FastColour.WhitePacked; - } - if (game.BlockInfo.Draw[block] == DrawType.Gas) return; - - lastTexId = -1; - atlas = game.TerrainAtlas1D; - bool sprite = game.BlockInfo.Draw[block] == DrawType.Sprite; - DrawParts(sprite); - if (index == 0) return; - - IGraphicsApi gfx = game.Graphics; - gfx.BindTexture(lastTexId); - - if (sprite) gfx.FaceCulling = true; - UpdateVB(); - if (sprite) gfx.FaceCulling = false; - } - - void FlushIfNotSame(int texIndex) { - int texId = game.TerrainAtlas1D.TexIds[texIndex]; - if (texId == lastTexId) return; - - if (lastTexId != -1) { - game.Graphics.BindTexture(lastTexId); - UpdateVB(); - } - lastTexId = texId; - index = 0; - } - - CuboidDrawer drawer = new CuboidDrawer(); - void DrawParts(bool sprite) { - // SwitchOrder is needed for held block, which renders without depth testing - if (sprite) { - if (SwitchOrder) { - SpriteZQuad(Side.Back, false); - SpriteXQuad(Side.Right, false); - } else { - SpriteXQuad(Side.Right, false); - SpriteZQuad(Side.Back, false); - } - - if (SwitchOrder) { - SpriteXQuad(Side.Right, true); - SpriteZQuad(Side.Back, true); - } else { - SpriteZQuad(Side.Back, true); - SpriteXQuad(Side.Right, true); - } - } else { - drawer.elementsPerAtlas1D = atlas.elementsPerAtlas1D; - drawer.invVerElementSize = atlas.invElementSize; - - drawer.minBB = game.BlockInfo.MinBB[block]; drawer.minBB.Y = 1 - drawer.minBB.Y; - drawer.maxBB = game.BlockInfo.MaxBB[block]; drawer.maxBB.Y = 1 - drawer.maxBB.Y; - - Vector3 min = game.BlockInfo.RenderMinBB[block]; - Vector3 max = game.BlockInfo.RenderMaxBB[block]; - drawer.x1 = min.X - 0.5f; drawer.y1 = min.Y; drawer.z1 = min.Z - 0.5f; - drawer.x2 = max.X - 0.5f; drawer.y2 = max.Y; drawer.z2 = max.Z - 0.5f; - - drawer.Tinted = game.BlockInfo.Tinted[block]; - drawer.TintColour = game.BlockInfo.FogColour[block]; - - drawer.Bottom(1, cols[1], GetTex(Side.Bottom), cache.vertices, ref index); - if (SwitchOrder) { - drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index); - drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index); - drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index); - drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index); - } else { - drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index); - drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index); - drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index); - drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index); - } - drawer.Top(1, cols[0], GetTex(Side.Top), cache.vertices, ref index); - } - } - - int GetTex(int side) { - int texId = game.BlockInfo.GetTextureLoc(block, side); - texIndex = texId / atlas.elementsPerAtlas1D; - - FlushIfNotSame(texIndex); - return texId; - } - - void SpriteZQuad(int side, bool firstPart) { - SpriteZQuad(side, firstPart, false); - SpriteZQuad(side, firstPart, true); - } - - void SpriteZQuad(int side, bool firstPart, bool mirror) { - int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0; - TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex); - FlushIfNotSame(texIndex); - if (height != 1) - rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f); - int col = cols[0]; - - float p1 = 0, p2 = 0; - if (firstPart) { // Need to break into two quads for when drawing a sprite model in hand. - if (mirror) { rec.U1 = 0.5f; p1 = -5.5f/16; } - else { rec.U2 = 0.5f; p2 = -5.5f/16; } - } else { - if (mirror) { rec.U2 = 0.5f; p2 = 5.5f/16; } - else { rec.U1 = 0.5f; p1 = 5.5f/16; } - } - - cache.vertices[index++] = new VertexP3fT2fC4b(p1, 0, p1, rec.U2, rec.V2, col); - cache.vertices[index++] = new VertexP3fT2fC4b(p1, 1, p1, rec.U2, rec.V1, col); - cache.vertices[index++] = new VertexP3fT2fC4b(p2, 1, p2, rec.U1, rec.V1, col); - cache.vertices[index++] = new VertexP3fT2fC4b(p2, 0, p2, rec.U1, rec.V2, col); - } - - void SpriteXQuad(int side, bool firstPart) { - SpriteXQuad(side, firstPart, false); - SpriteXQuad(side, firstPart, true); - } - - void SpriteXQuad(int side, bool firstPart, bool mirror) { - int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0; - TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex); - FlushIfNotSame(texIndex); - if (height != 1) - rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f); - int col = cols[0]; - - float x1 = 0, x2 = 0, z1 = 0, z2 = 0; - if (firstPart) { - if (mirror) { rec.U2 = 0.5f; x2 = -5.5f/16; z2 = 5.5f/16; } - else { rec.U1 = 0.5f; x1 = -5.5f/16; z1 = 5.5f/16; } - } else { - if (mirror) { rec.U1 = 0.5f; x1 = 5.5f/16; z1 = -5.5f/16; } - else { rec.U2 = 0.5f; x2 = 5.5f/16; z2 = -5.5f/16; } - } - - cache.vertices[index++] = new VertexP3fT2fC4b(x1, 0, z1, rec.U2, rec.V2, col); - cache.vertices[index++] = new VertexP3fT2fC4b(x1, 1, z1, rec.U2, rec.V1, col); - cache.vertices[index++] = new VertexP3fT2fC4b(x2, 1, z2, rec.U1, rec.V1, col); - cache.vertices[index++] = new VertexP3fT2fC4b(x2, 0, z2, rec.U1, rec.V2, col); - } - } +// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +using System; +using ClassicalSharp.Entities; +using ClassicalSharp.GraphicsAPI; +using ClassicalSharp.Physics; +using ClassicalSharp.Renderers; +using ClassicalSharp.Textures; +using OpenTK; + +#if USE16_BIT +using BlockID = System.UInt16; +#else +using BlockID = System.Byte; +#endif + +namespace ClassicalSharp.Model { + + public class BlockModel : IModel { + + BlockID block = Block.Air; + float height; + TerrainAtlas1D atlas; + Vector3 minBB, maxBB; + public bool SwitchOrder = false; + ModelCache cache; + + public BlockModel(Game game) : base(game) { + cache = game.ModelCache; + Bobbing = false; + UsesSkin = false; + } + + public override void CreateParts() { } + + public override float NameYOffset { get { return height + 0.075f; } } + + public override float GetEyeY(Entity entity) { + BlockID block = Byte.Parse(entity.ModelName); + float minY = game.BlockInfo.MinBB[block].Y; + float maxY = game.BlockInfo.MaxBB[block].Y; + return block == 0 ? 1 : (minY + maxY) / 2; + } + + static Vector3 colShrink = new Vector3(0.75f/16, 0.75f/16, 0.75f/16); + public override Vector3 CollisionSize { + get { return (maxBB - minBB) - colShrink; } // to fit slightly inside + } + + static Vector3 offset = new Vector3(-0.5f, 0.0f, -0.5f); + public override AABB PickingBounds { + get { return new AABB(minBB, maxBB).Offset(offset); } + } + + public void CalcState(BlockID block) { + if (game.BlockInfo.Draw[block] == DrawType.Gas) { + minBB = Vector3.Zero; + maxBB = Vector3.One; + height = 1; + } else { + minBB = game.BlockInfo.MinBB[block]; + maxBB = game.BlockInfo.MaxBB[block]; + height = maxBB.Y - minBB.Y; + if (game.BlockInfo.Draw[block] == DrawType.Sprite) + height = 1; + } + } + + public override float RenderDistance(Entity p) { + block = Utils.FastByte(p.ModelName); + CalcState(block); + return base.RenderDistance(p); + } + + int lastTexId = -1; + public override void DrawModel(Entity p) { + // TODO: using 'is' is ugly, but means we can avoid creating + // a string every single time held block changes. + if (p is FakePlayer) { + block = ((FakePlayer)p).Block; + } else { + block = Utils.FastByte(p.ModelName); + } + + CalcState(block); + if (game.BlockInfo.FullBright[block]) { + for (int i = 0; i < cols.Length; i++) + cols[i] = FastColour.WhitePacked; + } + if (game.BlockInfo.Draw[block] == DrawType.Gas) return; + + lastTexId = -1; + atlas = game.TerrainAtlas1D; + bool sprite = game.BlockInfo.Draw[block] == DrawType.Sprite; + DrawParts(sprite); + if (index == 0) return; + + IGraphicsApi gfx = game.Graphics; + gfx.BindTexture(lastTexId); + + if (sprite) gfx.FaceCulling = true; + UpdateVB(); + if (sprite) gfx.FaceCulling = false; + } + + void FlushIfNotSame(int texIndex) { + int texId = game.TerrainAtlas1D.TexIds[texIndex]; + if (texId == lastTexId) return; + + if (lastTexId != -1) { + game.Graphics.BindTexture(lastTexId); + UpdateVB(); + } + lastTexId = texId; + index = 0; + } + + CuboidDrawer drawer = new CuboidDrawer(); + void DrawParts(bool sprite) { + // SwitchOrder is needed for held block, which renders without depth testing + if (sprite) { + if (SwitchOrder) { + SpriteZQuad(Side.Back, false); + SpriteXQuad(Side.Right, false); + } else { + SpriteXQuad(Side.Right, false); + SpriteZQuad(Side.Back, false); + } + + if (SwitchOrder) { + SpriteXQuad(Side.Right, true); + SpriteZQuad(Side.Back, true); + } else { + SpriteZQuad(Side.Back, true); + SpriteXQuad(Side.Right, true); + } + } else { + drawer.elementsPerAtlas1D = atlas.elementsPerAtlas1D; + drawer.invVerElementSize = atlas.invElementSize; + + drawer.minBB = game.BlockInfo.MinBB[block]; drawer.minBB.Y = 1 - drawer.minBB.Y; + drawer.maxBB = game.BlockInfo.MaxBB[block]; drawer.maxBB.Y = 1 - drawer.maxBB.Y; + + Vector3 min = game.BlockInfo.RenderMinBB[block]; + Vector3 max = game.BlockInfo.RenderMaxBB[block]; + drawer.x1 = min.X - 0.5f; drawer.y1 = min.Y; drawer.z1 = min.Z - 0.5f; + drawer.x2 = max.X - 0.5f; drawer.y2 = max.Y; drawer.z2 = max.Z - 0.5f; + + drawer.Tinted = game.BlockInfo.Tinted[block]; + drawer.TintColour = game.BlockInfo.FogColour[block]; + + drawer.Bottom(1, cols[1], GetTex(Side.Bottom), cache.vertices, ref index); + if (SwitchOrder) { + drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index); + drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index); + drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index); + drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index); + } else { + drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index); + drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index); + drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index); + drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index); + } + drawer.Top(1, cols[0], GetTex(Side.Top), cache.vertices, ref index); + } + } + + int GetTex(int side) { + int texId = game.BlockInfo.GetTextureLoc(block, side); + texIndex = texId / atlas.elementsPerAtlas1D; + + FlushIfNotSame(texIndex); + return texId; + } + + void SpriteZQuad(int side, bool firstPart) { + SpriteZQuad(side, firstPart, false); + SpriteZQuad(side, firstPart, true); + } + + void SpriteZQuad(int side, bool firstPart, bool mirror) { + int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0; + TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex); + FlushIfNotSame(texIndex); + if (height != 1) + rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f); + int col = cols[0]; + + float p1 = 0, p2 = 0; + if (firstPart) { // Need to break into two quads for when drawing a sprite model in hand. + if (mirror) { rec.U1 = 0.5f; p1 = -5.5f/16; } + else { rec.U2 = 0.5f; p2 = -5.5f/16; } + } else { + if (mirror) { rec.U2 = 0.5f; p2 = 5.5f/16; } + else { rec.U1 = 0.5f; p1 = 5.5f/16; } + } + + cache.vertices[index++] = new VertexP3fT2fC4b(p1, 0, p1, rec.U2, rec.V2, col); + cache.vertices[index++] = new VertexP3fT2fC4b(p1, 1, p1, rec.U2, rec.V1, col); + cache.vertices[index++] = new VertexP3fT2fC4b(p2, 1, p2, rec.U1, rec.V1, col); + cache.vertices[index++] = new VertexP3fT2fC4b(p2, 0, p2, rec.U1, rec.V2, col); + } + + void SpriteXQuad(int side, bool firstPart) { + SpriteXQuad(side, firstPart, false); + SpriteXQuad(side, firstPart, true); + } + + void SpriteXQuad(int side, bool firstPart, bool mirror) { + int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0; + TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex); + FlushIfNotSame(texIndex); + if (height != 1) + rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f); + int col = cols[0]; + + float x1 = 0, x2 = 0, z1 = 0, z2 = 0; + if (firstPart) { + if (mirror) { rec.U2 = 0.5f; x2 = -5.5f/16; z2 = 5.5f/16; } + else { rec.U1 = 0.5f; x1 = -5.5f/16; z1 = 5.5f/16; } + } else { + if (mirror) { rec.U1 = 0.5f; x1 = 5.5f/16; z1 = -5.5f/16; } + else { rec.U2 = 0.5f; x2 = 5.5f/16; z2 = -5.5f/16; } + } + + cache.vertices[index++] = new VertexP3fT2fC4b(x1, 0, z1, rec.U2, rec.V2, col); + cache.vertices[index++] = new VertexP3fT2fC4b(x1, 1, z1, rec.U2, rec.V1, col); + cache.vertices[index++] = new VertexP3fT2fC4b(x2, 1, z2, rec.U1, rec.V1, col); + cache.vertices[index++] = new VertexP3fT2fC4b(x2, 0, z2, rec.U1, rec.V2, col); + } + } } \ No newline at end of file diff --git a/ClassicalSharp/Utils/Utils.cs b/ClassicalSharp/Utils/Utils.cs index 75013efd3..1e6b47b23 100644 --- a/ClassicalSharp/Utils/Utils.cs +++ b/ClassicalSharp/Utils/Utils.cs @@ -1,246 +1,243 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; -using System.Drawing; -using System.Globalization; -using ClassicalSharp.Model; -using OpenTK; -using OpenTK.Input; -#if ANDROID -using Android.Graphics; -using AndroidColor = Android.Graphics.Color; -#endif - -namespace ClassicalSharp { - - // NOTE: These delegates should be removed when using versions later than NET 2.0. - // ################################################################ - public delegate void Action(); - public delegate void Action(T1 arg1, T2 arg2); - public delegate void Action(T1 arg1, T2 arg2, T3 arg3); - public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - public delegate TResult Func(); - public delegate TResult Func(T1 arg1); - public delegate TResult Func(T1 arg1, T2 arg2); - public delegate TResult Func(T1 arg1, T2 arg2, T3 arg3); - // ################################################################ - - public static partial class Utils { - - public const int StringLength = 64; - - /// Returns a string with all the colour codes stripped from it. - public static string StripColours(string value) { - if (value.IndexOf('&') == -1) return value; - char[] output = new char[value.Length]; - int usedChars = 0; - - for (int i = 0; i < value.Length; i++) { - char token = value[i]; - if (token == '&') { - i++; // Skip over the following colour code. - } else { - output[usedChars++] = token; - } - } - return new String(output, 0, usedChars); - } - - /// Returns a string with a + removed if it is the last character in the string. - public static string RemoveEndPlus(string value) { - // Workaround for MCDzienny (and others) use a '+' at the end to distinguish classicube.net accounts - // from minecraft.net accounts. Unfortunately they also send this ending + to the client. - if (String.IsNullOrEmpty(value)) return value; - - return value[value.Length - 1] == '+' ? - value.Substring(0, value.Length - 1) : value; - } - - const StringComparison comp = StringComparison.OrdinalIgnoreCase; - /// Returns whether a equals b, ignoring any case differences. - public static bool CaselessEquals(string a, string b) { return a.Equals(b, comp); } - - /// Returns whether a starts with b, ignoring any case differences. - public static bool CaselessStarts(string a, string b) { return a.StartsWith(b, comp); } - - /// Returns whether a ends with b, ignoring any case differences. - public static bool CaselessEnds(string a, string b) { return a.EndsWith(b, comp); } - - /// Converts the given byte array of length N to a hex string of length 2N. - public static string ToHexString(byte[] array) { - int len = array.Length; - char[] hex = new char[len * 2]; - for (int i = 0; i < array.Length; i++) { - int value = array[i]; - int hi = value >> 4, lo = value & 0x0F; - - // 48 = index of 0, 55 = index of (A - 10). - hex[i * 2 + 0] = hi < 10 ? (char)(hi + 48) : (char)(hi + 55); - hex[i * 2 + 1] = lo < 10 ? (char)(lo + 48) : (char)(lo + 55); - } - return new String(hex); - } - - /// Returns the hex code represented by the given character. - /// Throws FormatException if the input character isn't a hex code. - public static int ParseHex(char value) { - int hex; - if (!TryParseHex(value, out hex)) - throw new FormatException("Invalid hex code given: " + value); - return hex; - } - - /// Attempts to return the hex code represented by the given character. - public static bool TryParseHex(char value, out int hex) { - hex = 0; - if (value >= '0' && value <= '9') { - hex = (int)(value - '0'); - } else if (value >= 'a' && value <= 'f') { - hex = (int)(value - 'a') + 10; - } else if (value >= 'A' && value <= 'F') { - hex = (int)(value - 'A') + 10; - } else { - return false; - } - return true; - } - - /// Attempts to caselessly parse the given string as a Key enum member, - /// returning defValue if there was an error parsing. - public static bool TryParseEnum(string value, T defValue, out T result) { - T mapping; - try { - mapping = (T)Enum.Parse(typeof(T), value, true); - } catch (ArgumentException) { - result = defValue; - return false; - } - result = mapping; - return true; - } - - public static void LogDebug(string text) { - Console.WriteLine(text); - } - - public static void LogDebug(string text, params object[] args) { - Console.WriteLine(String.Format(text, args)); - } - - public static int AdjViewDist(float value) { - return (int)(1.4142135 * value); - } - - /// Returns the number of vertices needed to subdivide a quad. - public static int CountVertices(int axis1Len, int axis2Len, int axisSize) { - return CeilDiv(axis1Len, axisSize) * CeilDiv(axis2Len, axisSize) * 4; - } - - public static int Tint(int col, FastColour tint) { - FastColour adjCol = FastColour.Unpack(col); - adjCol *= tint; - return adjCol.Pack(); - } - - public static byte FastByte(string s) { - int sum = 0; - switch (s.Length) { - case 1: sum = (s[0] - '0'); break; - case 2: sum = (s[0] - '0') * 10 + (s[1] - '0'); break; - case 3: sum = (s[0] - '0') * 100 + (s[1] - '0') * 10 + (s[2] - '0'); break; - } - return (byte)sum; - } - - /// Determines the skin type of the specified bitmap. - public static SkinType GetSkinType(Bitmap bmp) { - if (bmp.Width == bmp.Height * 2) { - return SkinType.Type64x32; - } else if (bmp.Width == bmp.Height) { - // Minecraft alex skins have this particular pixel with alpha of 0. - int scale = bmp.Width / 64; - - #if !ANDROID - int alpha = bmp.GetPixel(54 * scale, 20 * scale).A; - #else - int alpha = AndroidColor.GetAlphaComponent(bmp.GetPixel(54 * scale, 20 * scale)); - #endif - return alpha >= 127 ? SkinType.Type64x64 : SkinType.Type64x64Slim; - } - return SkinType.Invalid; - } - - /// Returns whether the specified string starts with http:// or https:// - public static bool IsUrlPrefix(string value, int index) { - int http = value.IndexOf("http://", index); - int https = value.IndexOf("https://", index); - return http == index || https == index; - } - - /// Conversion for code page 437 characters from index 0 to 31 to unicode. - public const string ControlCharReplacements = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"; - - /// Conversion for code page 437 characters from index 127 to 255 to unicode. - public const string ExtendedCharReplacements = "⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»" + - "░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌" + - "█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u00a0"; - - public static bool IsValidInputChar(char c, Game game) { - if (c >= ' ' && c <= '~') return true; // ascii - - bool isCP437 = Utils.ControlCharReplacements.IndexOf(c) >= 0 || - Utils.ExtendedCharReplacements.IndexOf(c) >= 0; - bool supportsCP437 = game.Server.SupportsFullCP437; - return supportsCP437 && isCP437; - } - - public unsafe static string ToLower(string value) { - fixed(char* ptr = value) { - for (int i = 0; i < value.Length; i++) { - char c = ptr[i]; - if (c < 'A' || c > 'Z') continue; - c += ' '; ptr[i] = c; - } - } - return value; - } - - // Not all languages use . as their decimal point separator - public static bool TryParseDecimal(string s, out float result) { - if (s.IndexOf(',') >= 0) - s = s.Replace(',', '.'); - float temp; - - result = 0; - if (!Single.TryParse(s, style, NumberFormatInfo.InvariantInfo, out temp)) return false; - if (Single.IsInfinity(temp) || Single.IsNaN(temp)) return false; - result = temp; - return true; - } - - public static float ParseDecimal(string s) { - if (s.IndexOf(',') >= 0) - s = s.Replace(',', '.'); - return Single.Parse(s, style, NumberFormatInfo.InvariantInfo); - } - - const NumberStyles style = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite - | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint; - - - #if USE16_BIT - public static ushort[] UInt8sToUInt16s(byte[] src) { - ushort[] dst = new ushort[src.Length]; - for (int i = 0; i < dst.Length; i++) - dst[i] = src[i]; - return dst; - } - - public static byte[] UInt16sToUInt8s(ushort[] src) { - byte[] dst = new byte[src.Length]; - for (int i = 0; i < dst.Length; i++) - dst[i] = (byte)src[i]; - return dst; - } - #endif - } +// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +using System; +using System.Drawing; +using System.Globalization; +using ClassicalSharp.Model; +using OpenTK; +using OpenTK.Input; +#if ANDROID +using Android.Graphics; +using AndroidColor = Android.Graphics.Color; +#endif + +namespace ClassicalSharp { + + // NOTE: These delegates should be removed when using versions later than NET 2.0. + // ################################################################ + public delegate void Action(); + public delegate void Action(T1 arg1, T2 arg2); + public delegate void Action(T1 arg1, T2 arg2, T3 arg3); + public delegate TResult Func(); + public delegate TResult Func(T1 arg1); + // ################################################################ + + public static partial class Utils { + + public const int StringLength = 64; + + /// Returns a string with all the colour codes stripped from it. + public static string StripColours(string value) { + if (value.IndexOf('&') == -1) return value; + char[] output = new char[value.Length]; + int usedChars = 0; + + for (int i = 0; i < value.Length; i++) { + char token = value[i]; + if (token == '&') { + i++; // Skip over the following colour code. + } else { + output[usedChars++] = token; + } + } + return new String(output, 0, usedChars); + } + + /// Returns a string with a + removed if it is the last character in the string. + public static string RemoveEndPlus(string value) { + // Workaround for MCDzienny (and others) use a '+' at the end to distinguish classicube.net accounts + // from minecraft.net accounts. Unfortunately they also send this ending + to the client. + if (String.IsNullOrEmpty(value)) return value; + + return value[value.Length - 1] == '+' ? + value.Substring(0, value.Length - 1) : value; + } + + const StringComparison comp = StringComparison.OrdinalIgnoreCase; + /// Returns whether a equals b, ignoring any case differences. + public static bool CaselessEquals(string a, string b) { return a.Equals(b, comp); } + + /// Returns whether a starts with b, ignoring any case differences. + public static bool CaselessStarts(string a, string b) { return a.StartsWith(b, comp); } + + /// Returns whether a ends with b, ignoring any case differences. + public static bool CaselessEnds(string a, string b) { return a.EndsWith(b, comp); } + + /// Converts the given byte array of length N to a hex string of length 2N. + public static string ToHexString(byte[] array) { + int len = array.Length; + char[] hex = new char[len * 2]; + for (int i = 0; i < array.Length; i++) { + int value = array[i]; + int hi = value >> 4, lo = value & 0x0F; + + // 48 = index of 0, 55 = index of (A - 10). + hex[i * 2 + 0] = hi < 10 ? (char)(hi + 48) : (char)(hi + 55); + hex[i * 2 + 1] = lo < 10 ? (char)(lo + 48) : (char)(lo + 55); + } + return new String(hex); + } + + /// Returns the hex code represented by the given character. + /// Throws FormatException if the input character isn't a hex code. + public static int ParseHex(char value) { + int hex; + if (!TryParseHex(value, out hex)) + throw new FormatException("Invalid hex code given: " + value); + return hex; + } + + /// Attempts to return the hex code represented by the given character. + public static bool TryParseHex(char value, out int hex) { + hex = 0; + if (value >= '0' && value <= '9') { + hex = (int)(value - '0'); + } else if (value >= 'a' && value <= 'f') { + hex = (int)(value - 'a') + 10; + } else if (value >= 'A' && value <= 'F') { + hex = (int)(value - 'A') + 10; + } else { + return false; + } + return true; + } + + /// Attempts to caselessly parse the given string as a Key enum member, + /// returning defValue if there was an error parsing. + public static bool TryParseEnum(string value, T defValue, out T result) { + T mapping; + try { + mapping = (T)Enum.Parse(typeof(T), value, true); + } catch (ArgumentException) { + result = defValue; + return false; + } + result = mapping; + return true; + } + + public static void LogDebug(string text) { + Console.WriteLine(text); + } + + public static void LogDebug(string text, params object[] args) { + Console.WriteLine(String.Format(text, args)); + } + + public static int AdjViewDist(float value) { + return (int)(1.4142135 * value); + } + + /// Returns the number of vertices needed to subdivide a quad. + public static int CountVertices(int axis1Len, int axis2Len, int axisSize) { + return CeilDiv(axis1Len, axisSize) * CeilDiv(axis2Len, axisSize) * 4; + } + + public static int Tint(int col, FastColour tint) { + FastColour adjCol = FastColour.Unpack(col); + adjCol *= tint; + return adjCol.Pack(); + } + + public static byte FastByte(string s) { + int sum = 0; + switch (s.Length) { + case 1: sum = (s[0] - '0'); break; + case 2: sum = (s[0] - '0') * 10 + (s[1] - '0'); break; + case 3: sum = (s[0] - '0') * 100 + (s[1] - '0') * 10 + (s[2] - '0'); break; + } + return (byte)sum; + } + + /// Determines the skin type of the specified bitmap. + public static SkinType GetSkinType(Bitmap bmp) { + if (bmp.Width == bmp.Height * 2) { + return SkinType.Type64x32; + } else if (bmp.Width == bmp.Height) { + // Minecraft alex skins have this particular pixel with alpha of 0. + int scale = bmp.Width / 64; + + #if !ANDROID + int alpha = bmp.GetPixel(54 * scale, 20 * scale).A; + #else + int alpha = AndroidColor.GetAlphaComponent(bmp.GetPixel(54 * scale, 20 * scale)); + #endif + return alpha >= 127 ? SkinType.Type64x64 : SkinType.Type64x64Slim; + } + return SkinType.Invalid; + } + + /// Returns whether the specified string starts with http:// or https:// + public static bool IsUrlPrefix(string value, int index) { + int http = value.IndexOf("http://", index); + int https = value.IndexOf("https://", index); + return http == index || https == index; + } + + /// Conversion for code page 437 characters from index 0 to 31 to unicode. + public const string ControlCharReplacements = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"; + + /// Conversion for code page 437 characters from index 127 to 255 to unicode. + public const string ExtendedCharReplacements = "⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»" + + "░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌" + + "█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u00a0"; + + public static bool IsValidInputChar(char c, Game game) { + if (c >= ' ' && c <= '~') return true; // ascii + + bool isCP437 = Utils.ControlCharReplacements.IndexOf(c) >= 0 || + Utils.ExtendedCharReplacements.IndexOf(c) >= 0; + bool supportsCP437 = game.Server.SupportsFullCP437; + return supportsCP437 && isCP437; + } + + public unsafe static string ToLower(string value) { + fixed(char* ptr = value) { + for (int i = 0; i < value.Length; i++) { + char c = ptr[i]; + if (c < 'A' || c > 'Z') continue; + c += ' '; ptr[i] = c; + } + } + return value; + } + + // Not all languages use . as their decimal point separator + public static bool TryParseDecimal(string s, out float result) { + if (s.IndexOf(',') >= 0) + s = s.Replace(',', '.'); + float temp; + + result = 0; + if (!Single.TryParse(s, style, NumberFormatInfo.InvariantInfo, out temp)) return false; + if (Single.IsInfinity(temp) || Single.IsNaN(temp)) return false; + result = temp; + return true; + } + + public static float ParseDecimal(string s) { + if (s.IndexOf(',') >= 0) + s = s.Replace(',', '.'); + return Single.Parse(s, style, NumberFormatInfo.InvariantInfo); + } + + const NumberStyles style = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite + | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint; + + + #if USE16_BIT + public static ushort[] UInt8sToUInt16s(byte[] src) { + ushort[] dst = new ushort[src.Length]; + for (int i = 0; i < dst.Length; i++) + dst[i] = src[i]; + return dst; + } + + public static byte[] UInt16sToUInt8s(ushort[] src) { + byte[] dst = new byte[src.Length]; + for (int i = 0; i < dst.Length; i++) + dst[i] = (byte)src[i]; + return dst; + } + #endif + } } \ No newline at end of file diff --git a/src/Client/Client.vcxproj b/src/Client/Client.vcxproj index 0e7b609ed..66a64fd1e 100644 --- a/src/Client/Client.vcxproj +++ b/src/Client/Client.vcxproj @@ -173,9 +173,12 @@ + + + @@ -201,9 +204,9 @@ + - diff --git a/src/Client/Client.vcxproj.filters b/src/Client/Client.vcxproj.filters index c6ef0c7b0..265a05251 100644 --- a/src/Client/Client.vcxproj.filters +++ b/src/Client/Client.vcxproj.filters @@ -70,6 +70,9 @@ {cb26e83f-9153-4964-9dc2-13502fbb1d1b} + + {a912f0d5-3ceb-4e7e-ba4e-28170c634047} + @@ -156,6 +159,15 @@ Header Files\Map + + Header Files\Graphics + + + Header Files\Events + + + Header Files\Events + @@ -203,9 +215,6 @@ Source Files\Graphics - - Source Files\Graphics - Source Files\Math @@ -218,5 +227,8 @@ Source Files\Map + + Source Files\Graphics + \ No newline at end of file diff --git a/src/Client/D3D9Api.c b/src/Client/D3D9Api.c index cc8c27fdb..ae38a2e28 100644 --- a/src/Client/D3D9Api.c +++ b/src/Client/D3D9Api.c @@ -22,6 +22,13 @@ void Gfx_Init(Game* game) { } +bool Gfx_SetTexturing(bool enabled) { + if (enabled) return; + ReturnCode hresult = IDirect3DDevice9_SetTexture(device, 0, NULL); + ErrorHandler_CheckOrFail(hresult, "D3D9_SetTexturing"); +} + + bool d3d9_fogEnable = false; void Gfx_SetFog(bool enabled) { if (d3d9_fogEnable == enabled) return; diff --git a/src/Client/EventHandler.h b/src/Client/EventHandler.h new file mode 100644 index 000000000..76550b98b --- /dev/null +++ b/src/Client/EventHandler.h @@ -0,0 +1,37 @@ +#ifndef CS_EVENTHANDLER_H +#define CS_EVENTHANDLER_H +#include "Typedefs.h" +/* Helper method for managing events. + Copyright 2017 ClassicalSharp | Licensed under BSD-3 +*/ + + +/* Event handler that takes no arguments. */ +typedef void (*Event_Void)(void); + +/* Event handler that takes single 32 bit signed integer argument. */ +typedef void (*Event_Int32)(Int32 argument); + +/* Event handler that takes single floating-point argument. */ +typedef void(*Event_Float32)(Real32 argument); + + +/* Maximum number of event handlers that can be registered. */ +#define EventHandler_Size 32 + +/* Adds given event handler to handlers list for the given event. */ +void EventHandler_Register(void** handlers, Int32* count, void* handler); + +/* Removes given event handler from handlers list of the given event. */ +void EventHandler_Unregister(void** handlers, Int32* count, void* handler); + + +/* Calls handlers for an event that has no arguments.*/ +void EventHandler_Call_Void(Event_Void* handlers, Int32 handlersCount); + +/* Calls handlers for an event that has no arguments.*/ +void EventHandler_Call_Int32(Event_Int32* handlers, Int32 handlersCount); + +/* Calls handlers for an event that has no arguments.*/ +void EventHandler_Call_Float32(Event_Float32* handlers, Int32 handlersCount); +#endif \ No newline at end of file diff --git a/src/Client/GraphicsAPI.h b/src/Client/GraphicsAPI.h index 2880b585a..3f6d6f6dd 100644 --- a/src/Client/GraphicsAPI.h +++ b/src/Client/GraphicsAPI.h @@ -1,6 +1,7 @@ #ifndef CS_GFXAPI_H #define CS_GFXAPI_H #include "Typedefs.h" +#include "EventHandler.h" #include "Bitmap.h" #include "FastColour.h" #include "String.h" @@ -16,26 +17,28 @@ void Gfx_Init(Game* game); /* Maximum supported length of a dimension (width and height) of a 2D texture. */ -Int32 Gfx_MaxTextureDimensions; +Int32 Gfx_MaxTextureDimensions = 0; /* Minimum near plane value supported by the graphics API. */ -float Gfx_MinZNear; +float Gfx_MinZNear = 0.0f; /* Returns whether this graphics api had a valid context. */ -bool Gfx_LostContext; +bool Gfx_LostContext = false; /* Maximum number of vertices that can be indexed. */ #define Gfx_MaxIndices (65536 / 4 * 6) - // TODO: define these, we need an action interface - /*/// Event raised when a context is destroyed after having been previously lost. - public event Action ContextLost; +/* Event raised when a context is destroyed after having been previously lost. */ +Event_Void Gfx_ContextLost[EventHandler_Size]; +Int32 Gfx_ContextLostCount = 0; - /// Event raised when a context is recreated after having been previously lost. - public event Action ContextRecreated; +/* Event raised when a context is recreated after having been previously lost. */ +Event_Void Gfx_ContextRecreated[EventHandler_Size]; +Int32 Gfx_ContextRecreatedCount = 0; - /// Delegate that is invoked when the current context is lost, +// TODO: IMPLEMENT THIS +/* /// Delegate that is invoked when the current context is lost, /// and is repeatedly invoked until the context can be retrieved. public Action LostContextFunction;*/ diff --git a/src/Client/GraphicsCommon.c b/src/Client/GraphicsCommon.c index 903975ee7..2e109e85f 100644 --- a/src/Client/GraphicsCommon.c +++ b/src/Client/GraphicsCommon.c @@ -19,7 +19,7 @@ void GfxCommon_LoseContext(String reason) { Platform_Log(String_FromConstant("Lost graphics context:")); Platform_Log(reason); - if (ContextLost != null) ContextLost(); + EventHandler_Call_Void(Gfx_ContextLost, Gfx_ContextLostCount); GfxCommon_Free(); } @@ -27,7 +27,7 @@ void GfxCommon_RecreateContext() { Gfx_LostContext = false; Platform_Log(String_FromConstant("Recreating graphics context")); - if (ContextRecreated != null) ContextRecreated(); + EventHandler_Call_Void(Gfx_ContextRecreated, Gfx_ContextRecreatedCount); GfxCommon_Init(); } diff --git a/src/Client/World.c b/src/Client/World.c index 7ab7b1fa8..57d7500de 100644 --- a/src/Client/World.c +++ b/src/Client/World.c @@ -2,11 +2,12 @@ #include "BlockID.h" #include "ErrorHandler.h" #include "String.h" +#include "WorldEnv.h" void World_Reset() { World_Width = 0; World_Height = 0; World_Length = 0; World_Blocks = NULL; World_BlocksSize = 0; - Uuid = Guid.NewGuid(); + World_Uuid = Guid.NewGuid(); } void World_SetNewMap(BlockID* blocks, Int32 blocksSize, Int32 width, Int32 height, Int32 length) { @@ -18,8 +19,12 @@ void World_SetNewMap(BlockID* blocks, Int32 blocksSize, Int32 width, Int32 heigh ErrorHandler_Fail(String_FromConstant("Blocks array size does not match volume of map.")); } - if (Env.EdgeHeight == -1) Env.EdgeHeight = height / 2; - if (Env.CloudHeight == -1) Env.CloudHeight = height + 2; + if (WorldEnv_EdgeHeight == -1) { + WorldEnv_EdgeHeight = height / 2; + } + if (WorldEnv_CloudHeight == -1) { + WorldEnv_CloudHeight = height + 2; + } } BlockID World_GetPhysicsBlock(Int32 x, Int32 y, Int32 z) { diff --git a/src/Client/World.h b/src/Client/World.h index 7d6916e50..e5dfdc5af 100644 --- a/src/Client/World.h +++ b/src/Client/World.h @@ -66,5 +66,5 @@ bool World_IsValidPos(Int32 x, Int32 y, Int32 z); bool World_IsValidPos_3I(Vector3I p); /* Unpacks the given index into the map's block array into its original world coordinates. */ -Vector3I World_GetCoords(int index); +Vector3I World_GetCoords(Int32 index); #endif \ No newline at end of file diff --git a/src/Client/WorldEvents.h b/src/Client/WorldEvents.h new file mode 100644 index 000000000..8e43b99e9 --- /dev/null +++ b/src/Client/WorldEvents.h @@ -0,0 +1,61 @@ +#ifndef CS_WORLDEVENTS_H +#define CS_WORLDEVENTS_H +#include "EventHandler.h" +#include "Typedefs.h" +/* World related events. + Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +*/ + +/* Raised when the player joins and begins loading a new world. */ +Event_Void WorldEvents_NewMap[EventHandler_Size]; +Int32 WorldEvents_NewMapCount = 0; + +/* Raises NewMap event. */ +void WorldEvents_RaiseNewMap(); + + +/* Raised when a portion of the world is read and decompressed, or generated. +The floating point argument is progress (from 0 to 1). */ +Event_Float32 WorldEvents_MapLoading[EventHandler_Size]; +Int32 WorldEvents_MapLoadingCount = 0; + +/* Raises MapLoading event. */ +void WorldEvents_RaiseMapLoading(Real32 progress); + + +/* Raised when new world has finished loading and the player can now interact with it. */ +Event_Void WorldEvents_NewMapLoaded[EventHandler_Size]; +Int32 WorldEvents_NewMapLoadedCount = 0; + +/* Raises NewMapLoaded event. */ +void WorldEvents_RaiseNewMapLoaded(); + + +/* Raised when an environment variable of the world is changed by the user, CPE, or WoM config. */ +Event_Int32 WorldEvents_EnvVariableChanged[EventHandler_Size]; +Int32 WorldEvents_EnvVariableChangedCount = 0; + +/* Raises EnvVariableChanged event. */ +void WorldEvents_RaiseEnvVariableChanged(Int32 envVar); + + +/* Environment variable identifiers*/ + +#define EnvVar_SidesBlock 0 +#define EnvVar_EdgeBlock 1 +#define EnvVar_EdgeLevel 2 +#define EnvVar_CloudsLevel 3 +#define EnvVar_CloudsSpeed 4 +#define EnvVar_Weather 5 + +#define EnvVar_SkyCol 6 +#define EnvVar_CloudsCol 7 +#define EnvVar_FogCol 8 +#define EnvVar_SunCol 9 +#define EnvVar_ShadowCol 10 + +#define EnvVar_WeatherSpeed 11 +#define EnvVar_WeatherFade 12 +#define EnvVar_ExpFog 13 +#define EnvVar_SidesOffset 14 +#endif \ No newline at end of file