// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using System.Collections.Generic; using ClassicalSharp.Blocks; using OpenTK; using BlockID = System.UInt16; using TexLoc = System.UInt16; namespace ClassicalSharp { public static class SoundType { public const byte None = 0; public const byte Wood = 1; public const byte Gravel = 2; public const byte Grass = 3; public const byte Stone = 4; public const byte Metal = 5; public const byte Glass = 6; public const byte Cloth = 7; public const byte Sand = 8; public const byte Snow = 9; public const byte Count = 10; public static string[] Names = new string[Count] { "none", "wood", "gravel", "grass", "stone", "metal", "glass", "cloth", "sand", "snow", }; } /// Describes how a block is rendered in the world. public static class DrawType { /// Completely covers blocks behind (e.g. dirt). public const byte Opaque = 0; /// Blocks behind show (e.g. glass). Pixels are either fully visible or invisible. public const byte Transparent = 1; /// Same as Transparent, but all neighbour faces show. (e.g. leaves) public const byte TransparentThick = 2; /// Blocks behind show (e.g. water). Pixels blend with other blocks behind. public const byte Translucent = 3; /// Does not show (e.g. air). Can still be collided with. public const byte Gas = 4; /// Block renders as an X sprite (e.g. sapling). Pixels are either fully visible or invisible. public const byte Sprite = 5; } /// Describes the interaction a block has with a player when they collide with it. public static class CollideType { /// No interaction when player collides. public const byte Gas = 0; /// 'swimming'/'bobbing' interaction when player collides. public const byte Liquid = 1; /// Block completely stops the player when they are moving. public const byte Solid = 2; /// Block is solid and partially slidable on. public const byte Ice = 3; /// Block is solid and fully slidable on. public const byte SlipperyIce = 4; /// Water style 'swimming'/'bobbing' interaction when player collides. public const byte LiquidWater = 5; /// Lava style 'swimming'/'bobbing' interaction when player collides. public const byte LiquidLava = 6; /// Rope/Ladder style climbing interaction when player collides. public const byte ClimbRope = 7; } /// Stores various properties about the blocks. /// e.g. blocks light, height, texture IDs, etc. public static partial class BlockInfo { public static bool[] IsLiquid, BlocksLight, FullBright; public static bool[] CanPlace, CanDelete, Tinted, FullOpaque; public static byte[] Collide, ExtendedCollide, hidden; public static byte[] LightOffset, Draw, SpriteOffset, CanStretch; public static byte[] DigSounds, StepSounds; public static string[] Name; public static float[] FogDensity, SpeedMultiplier; public static TexLoc[] textures; public static PackedCol[] FogCol; public static Vector3[] MinBB, MaxBB, RenderMinBB, RenderMaxBB; static uint[] DefinedCustomBlocks; public static int MaxUsed, Count, IDMask; public static void Allocate(int count) { IsLiquid = new bool[count]; BlocksLight = new bool[count]; FullBright = new bool[count]; CanPlace = new bool[count]; CanDelete = new bool[count]; Tinted = new bool[count]; FullOpaque = new bool[count]; Collide = new byte[count]; ExtendedCollide = new byte[count]; textures = new TexLoc[count * Side.Sides]; hidden = new byte[count * count]; LightOffset = new byte[count]; Draw = new byte[count]; SpriteOffset = new byte[count]; CanStretch = new byte[count]; DigSounds = new byte[count]; StepSounds = new byte[count]; Name = new string[count]; FogDensity = new float[count]; SpeedMultiplier = new float[count]; FogCol = new PackedCol[count]; MinBB = new Vector3[count]; MaxBB = new Vector3[count]; RenderMinBB = new Vector3[count]; RenderMaxBB = new Vector3[count]; DefinedCustomBlocks = new uint[count >> 5]; Count = count; SetMaxUsed(count - 1); } public static void SetMaxUsed(int max) { MaxUsed = max; IDMask = Utils.NextPowerOf2(max + 1) - 1; } public static void Reset() { Init(); RecalculateSpriteBB(); } public static void Init() { for (int i = 0; i < DefinedCustomBlocks.Length; i++) { DefinedCustomBlocks[i] = 0; } for (int b = 0; b < Count; b++) { ResetBlockProps((BlockID)b); } UpdateCulling(); } public static void SetDefaultPerms() { for (int b = Block.Air; b < Count; b++) { CanPlace[b] = true; CanDelete[b] = true; } CanPlace[Block.Air] = false; CanDelete[Block.Air] = false; CanPlace[Block.Lava] = false; CanDelete[Block.Lava] = false; CanPlace[Block.Water] = false; CanDelete[Block.Water] = false; CanPlace[Block.StillLava] = false; CanDelete[Block.StillLava] = false; CanPlace[Block.StillWater] = false; CanDelete[Block.StillWater] = false; CanPlace[Block.Bedrock] = false; CanDelete[Block.Bedrock] = false; } public static bool IsCustomDefined(BlockID block) { return (DefinedCustomBlocks[block >> 5] & (1u << (block & 0x1F))) != 0; } public static void SetCustomDefined(BlockID block, bool defined) { if (defined) { DefinedCustomBlocks[block >> 5] |= (1u << (block & 0x1F)); } else { DefinedCustomBlocks[block >> 5] &= ~(1u << (block & 0x1F)); } } public static void DefineCustom(Game game, BlockID block) { SetBlockDraw(block, Draw[block]); CalcRenderBounds(block); UpdateCulling(block); Tinted[block] = FogCol[block] != PackedCol.Black && Name[block].IndexOf('#') >= 0; CalcLightOffset(block); game.Inventory.AddDefault(block); SetCustomDefined(block, true); Events.RaiseBlockDefinitionChanged(); } static void RecalcIsLiquid(BlockID block) { byte collide = ExtendedCollide[block]; IsLiquid[block] = (collide == CollideType.LiquidWater && Draw[block] == DrawType.Translucent) || (collide == CollideType.LiquidLava && Draw[block] == DrawType.Transparent); } public static void SetCollide(BlockID block, byte collide) { // necessary for cases where servers redefined core blocks before extended types were introduced collide = DefaultSet.MapOldCollide(block, collide); ExtendedCollide[block] = collide; RecalcIsLiquid(block); // Reduce extended collision types to their simpler forms if (collide == CollideType.Ice) collide = CollideType.Solid; if (collide == CollideType.SlipperyIce) collide = CollideType.Solid; if (collide == CollideType.LiquidWater) collide = CollideType.Liquid; if (collide == CollideType.LiquidLava) collide = CollideType.Liquid; Collide[block] = collide; } public static void SetBlockDraw(BlockID block, byte draw) { if (draw == DrawType.Opaque && Collide[block] != CollideType.Solid) draw = DrawType.Transparent; Draw[block] = draw; RecalcIsLiquid(block); FullOpaque[block] = draw == DrawType.Opaque && MinBB[block] == Vector3.Zero && MaxBB[block] == Vector3.One; } public static void ResetBlockProps(BlockID block) { BlocksLight[block] = DefaultSet.BlocksLight(block); FullBright[block] = DefaultSet.FullBright(block); FogCol[block] = DefaultSet.FogColour(block); FogDensity[block] = DefaultSet.FogDensity(block); SetCollide(block, DefaultSet.Collide(block)); DigSounds[block] = DefaultSet.DigSound(block); StepSounds[block] = DefaultSet.StepSound(block); SpeedMultiplier[block] = 1; Name[block] = DefaultName(block); Tinted[block] = false; SpriteOffset[block] = 0; Draw[block] = DefaultSet.Draw(block); if (Draw[block] == DrawType.Sprite) { MinBB[block] = new Vector3(2.50f/16f, 0, 2.50f/16f); MaxBB[block] = new Vector3(13.5f/16f, 1, 13.5f/16f); } else { MinBB[block] = Vector3.Zero; MaxBB[block] = Vector3.One; MaxBB[block].Y = DefaultSet.Height(block); } SetBlockDraw(block, Draw[block]); CalcRenderBounds(block); CalcLightOffset(block); if (block >= Block.CpeCount) { SetTex(0, Side.Top, block); SetTex(0, Side.Bottom, block); SetSide(0, block); } else { SetTex(topTex[block], Side.Top, block); SetTex(bottomTex[block], Side.Bottom, block); SetSide(sideTex[block], block); } } public static int FindID(string name) { for (int b = 0; b < Count; b++) { if (Utils.CaselessEq(Name[b], name)) return b; } return -1; } public static int Parse(string str) { int b; if (int.TryParse(str, out b) && b < Count) return b; return FindID(str); } const string RawNames = "Air_Stone_Grass_Dirt_Cobblestone_Wood_Sapling_Bedrock_Water_Still water_Lava" + "_Still lava_Sand_Gravel_Gold ore_Iron ore_Coal ore_Log_Leaves_Sponge_Glass_Red_Orange_Yellow_Lime_Green" + "_Teal_Aqua_Cyan_Blue_Indigo_Violet_Magenta_Pink_Black_Gray_White_Dandelion_Rose_Brown mushroom_Red mushroom" + "_Gold_Iron_Double slab_Slab_Brick_TNT_Bookshelf_Mossy rocks_Obsidian_Cobblestone slab_Rope_Sandstone" + "_Snow_Fire_Light pink_Forest green_Brown_Deep blue_Turquoise_Ice_Ceramic tile_Magma_Pillar_Crate_Stone brick"; static string DefaultName(BlockID block) { if (block >= Block.CpeCount) return "Invalid"; // Find start and end of this particular block name int start = 0; for (int i = 0; i < block; i++) start = RawNames.IndexOf('_', start) + 1; int end = RawNames.IndexOf('_', start); if (end == -1) end = RawNames.Length; return RawNames.Substring(start, end - start); } internal static void SetSide(TexLoc texLoc, BlockID blockId) { textures[blockId * Side.Sides + Side.Left] = texLoc; textures[blockId * Side.Sides + Side.Right] = texLoc; textures[blockId * Side.Sides + Side.Front] = texLoc; textures[blockId * Side.Sides + Side.Back] = texLoc; } internal static void SetTex(TexLoc texLoc, int face, BlockID blockId) { textures[blockId * Side.Sides + face] = texLoc; } public static int GetTextureLoc(BlockID block, int face) { return textures[block * Side.Sides + face]; } static byte[] topTex = new byte[] { 0, 1, 0, 2, 16, 4, 15, 17, 14, 14, 30, 30, 18, 19, 32, 33, 34, 21, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 24, 23, 6, 6, 7, 9, 4, 36, 37, 16, 11, 25, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 26, 53, 52, }; static byte[] sideTex = new byte[] { 0, 1, 3, 2, 16, 4, 15, 17, 14, 14, 30, 30, 18, 19, 32, 33, 34, 20, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 40, 39, 5, 5, 7, 8, 35, 36, 37, 16, 11, 41, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 42, 53, 52, }; static byte[] bottomTex = new byte[] { 0, 1, 2, 2, 16, 4, 15, 17, 14, 14, 30, 30, 18, 19, 32, 33, 34, 21, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 56, 55, 6, 6, 7, 10, 4, 36, 37, 16, 11, 57, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 58, 53, 52 }; internal static void UpdateCulling() { for (int block = 0; block < Count; block++) { CalcStretch((BlockID)block); for (int neighbour = 0; neighbour < Count; neighbour++) { CalcCulling((BlockID)block, (BlockID)neighbour); } } } internal static void UpdateCulling(BlockID block) { CalcStretch(block); for (int other = 0; other < Count; other++) { CalcCulling(block, (BlockID)other); CalcCulling((BlockID)other, block); } } static void CalcStretch(BlockID block) { // faces which can be stretched on X axis if (MinBB[block].X == 0 && MaxBB[block].X == 1) { CanStretch[block] |= 0x3C; } else { CanStretch[block] &= 0xC3; // ~0x3C } // faces which can be stretched on Z axis if (MinBB[block].Z == 0 && MaxBB[block].Z == 1) { CanStretch[block] |= 0x03; } else { CanStretch[block] &= 0xFC; // ~0x03 } } static void CalcCulling(BlockID block, BlockID other) { if (!IsHidden(block, other)) { // Block is not hidden at all, so we can just entirely skip per-face check BlockInfo.hidden[(block * Count) + other] = 0; } else { Vector3 bMin = MinBB[block], bMax = MaxBB[block]; Vector3 oMin = MinBB[other], oMax = MaxBB[other]; if (IsLiquid[block]) bMax.Y -= 1.5f/16; if (IsLiquid[other]) oMax.Y -= 1.5f/16; // Don't need to care about sprites here since they never cull faces bool bothLiquid = IsLiquid[block] && IsLiquid[other]; int f = 0; // mark all faces initially 'not hidden' // Whether the 'texture region' of a face on block fits inside corresponding region on other block bool occludedX = (bMin.Z >= oMin.Z && bMax.Z <= oMax.Z) && (bMin.Y >= oMin.Y && bMax.Y <= oMax.Y); bool occludedY = (bMin.X >= oMin.X && bMax.X <= oMax.X) && (bMin.Z >= oMin.Z && bMax.Z <= oMax.Z); bool occludedZ = (bMin.X >= oMin.X && bMax.X <= oMax.X) && (bMin.Y >= oMin.Y && bMax.Y <= oMax.Y); f |= occludedX && oMax.X == 1 && bMin.X == 0 ? (1 << Side.Left) : 0; f |= occludedX && oMin.X == 0 && bMax.X == 1 ? (1 << Side.Right) : 0; f |= occludedZ && oMax.Z == 1 && bMin.Z == 0 ? (1 << Side.Front) : 0; f |= occludedZ && oMin.Z == 0 && bMax.Z == 1 ? (1 << Side.Back) : 0; f |= occludedY && (bothLiquid || (oMax.Y == 1 && bMin.Y == 0)) ? (1 << Side.Bottom) : 0; f |= occludedY && (bothLiquid || (oMin.Y == 0 && bMax.Y == 1)) ? (1 << Side.Top) : 0; BlockInfo.hidden[(block * Count) + other] = (byte)f; } } static bool IsHidden(BlockID block, BlockID other) { // Sprite blocks can never hide faces. if (Draw[block] == DrawType.Sprite) return false; // NOTE: Water is always culled by lava if ((block == Block.Water || block == Block.StillWater) && (other == Block.Lava || other == Block.StillLava)) return true; // All blocks (except for say leaves) cull with themselves. if (block == other) return Draw[block] != DrawType.TransparentThick; // An opaque neighbour (asides from lava) culls the face. if (Draw[other] == DrawType.Opaque && !IsLiquid[other]) return true; if (Draw[block] != DrawType.Translucent || Draw[other] != DrawType.Translucent) return false; // e.g. for water / ice, don't need to draw water. byte bType = Collide[block], oType = Collide[other]; bool canSkip = (bType == CollideType.Solid && oType == CollideType.Solid) || bType != CollideType.Solid; return canSkip; } /// Returns whether the face at the given face of the block /// should be drawn with the neighbour 'other' present on the other side of the face. public static bool IsFaceHidden(BlockID block, BlockID other, int tileSide) { return (hidden[(block * Count) + other] & (1 << tileSide)) != 0; } } }