Exemple #1
0
 public BlockState this[Int3 location]
 {
     get {
         Int3 chunkSectionKey = location.GetChunk();
         Int2 chunkKey        = new Int2(chunkSectionKey.X, chunkSectionKey.Z);
         if (Chunks.ContainsKey(chunkKey))
         {
             ChunkColumn  chunk   = Chunks[chunkKey];
             ChunkSection section = chunk[chunkSectionKey.Y];
             if (section == null)
             {
                 return(new BlockState());
             }
             else
             {
                 Int3    bp = location.InChunkBlock();
                 BlockId id = section.GetBlock(bp.X, bp.Y, bp.Z);
                 return(GlobalPalette.GetState(id.StateId));
             }
         }
         else
         {
             return(new BlockState());
         }
     }
 }
Exemple #2
0
        public void SetBlock(int x, int y, int z, Block block)
        {
            x = NumericsHelper.Modulo(x, 16);
            z = NumericsHelper.Modulo(z, 16);
            var sectionIndex = y >> 4;

            var success = Sections[sectionIndex].SetBlock(x, y & 15, z, block);

            // Palette dynamic sizing
            if (!success)
            {
                var oldSection = Sections[sectionIndex];
                var bpb        = oldSection.BitsPerBlock;
                bpb += 1;
                var newSection = new ChunkSection(bpb, sectionIndex);
                for (int sx = 0; sx < 16; sx++)
                {
                    for (int sy = 0; sy < 16; sy++)
                    {
                        for (int sz = 0; sz < 16; sz++)
                        {
                            // Seems to be the safest way to do this. A bit expensive, though...
                            newSection.SetBlock(sx, sy, sz, oldSection.GetBlock(sx, sy, sz));
                        }
                    }
                }

                Sections[sectionIndex] = newSection;
                SetBlock(x, y, z, block);
            }
        }
Exemple #3
0
    private static void ChunkRecieve(Packet packet)
    {
        Vector3Int pos     = new Vector3Int(packet.ReadInt(), packet.ReadInt(), packet.ReadInt());
        bool       encoded = packet.ReadBool();
        int        count   = packet.ReadInt();

        byte[] data = packet.ReadBytes(count);

        Vector2Int parentPos = new Vector2Int(pos.x, pos.z);

        if (!loadingChunks.ContainsKey(parentPos))
        {
            loadingChunks[parentPos] = new Chunk(parentPos);
        }

        Chunk parent = loadingChunks[parentPos];

        parent.sections[pos.y] = ChunkSection.Decode(parent, pos, data, encoded);

        foreach (ChunkSection section in parent.sections)
        {
            if (section == null)
            {
                return;
            }
        }

        loadingChunks.Remove(parentPos);
        ChunkManager.Get.AddChunk(parent);
    }
        protected override void GenerateChunk(ChunkSection chunk, int x, int z)
        {
            var localPos = new Coord3(x, 0, z);
            var pos      = localPos.BlockToWorld(chunk.worldPosition);

            var height = baseHeight + GetNoise(pos.x, 0, pos.z, noiseFreq, noiseHeight);

            for (int y = 0; y < ChunkSection.Size; y++)
            {
                localPos.y = y;
                pos        = localPos.BlockToWorld(chunk.worldPosition);

                if (chanceNoise)
                {
                    if (noise3d || pos.y == 0)
                    {
                        var chance = GetChance(pos.x, pos.y, pos.z, freq, dens);
                        if (chance)
                        {
                            SetBlock(chunk, localPos, dirt);
                            SetBlock(chunk, new Coord3(localPos.x, localPos.y + 1, localPos.z), stone);
                        }
                    }
                }
            }
        }
Exemple #5
0
 public void Enqueue(ChunkSection c)
 {
     lock (toLoad)
     {
         c.Meshed = true;          // TODO Leave here?
         toLoad.Enqueue(c.Copy()); // TODO Copy maybe? to not have to worry about threading access
     }
 }
Exemple #6
0
        public void ReadError()
        {
            ChunkSection chunkSection = new ChunkSection();

            var data = Convert.FromBase64String(ResourseReader.Read("ChunkSectionError1.txt"));

            chunkSection.Parse(ref data);
        }
Exemple #7
0
        private void ProcessNode(IBlockAccess level, ChunkColumn chunk, BlockCoordinates coordinates, Queue <BlockCoordinates> lightBfsQueue, HashSet <BlockCoordinates> lightBfSet)
        {
            //if (section.IsAllAir())

            byte currentSkyLight = GetSkyLight(coordinates, chunk);

            int          sectionIdx = coordinates.Y >> 4;
            ChunkSection subChunk   = (ChunkSection)chunk.GetSection(coordinates.Y);

            byte maxSkyLight = currentSkyLight;

            if (coordinates.Y < 255)
            {
                var up = coordinates.BlockUp();
                maxSkyLight = Math.Max(maxSkyLight, SetLightLevel(level, chunk, subChunk, sectionIdx, lightBfsQueue, lightBfSet, up, currentSkyLight, up: true));
            }

            if (coordinates.Y > 0)
            {
                var down = coordinates.BlockDown();
                maxSkyLight = Math.Max(maxSkyLight, SetLightLevel(level, chunk, subChunk, sectionIdx, lightBfsQueue, lightBfSet, down, currentSkyLight, down: true));
            }

            var west = coordinates.BlockWest();

            maxSkyLight = Math.Max(maxSkyLight, SetLightLevel(level, chunk, subChunk, sectionIdx, lightBfsQueue, lightBfSet, west, currentSkyLight));


            var east = coordinates.BlockEast();

            maxSkyLight = Math.Max(maxSkyLight, SetLightLevel(level, chunk, subChunk, sectionIdx, lightBfsQueue, lightBfSet, east, currentSkyLight));


            var south = coordinates.BlockSouth();

            maxSkyLight = Math.Max(maxSkyLight, SetLightLevel(level, chunk, subChunk, sectionIdx, lightBfsQueue, lightBfSet, south, currentSkyLight));

            var north = coordinates.BlockNorth();

            maxSkyLight = Math.Max(maxSkyLight, SetLightLevel(level, chunk, subChunk, sectionIdx, lightBfsQueue, lightBfSet, north, currentSkyLight));

            if (IsTransparent(coordinates, subChunk) && currentSkyLight != 15)
            {
                int diffuseLevel = GetDiffuseLevel(coordinates, subChunk);
                maxSkyLight = (byte)Math.Max(currentSkyLight, maxSkyLight - diffuseLevel);

                if (maxSkyLight > currentSkyLight)
                {
                    level.SetSkyLight(coordinates, maxSkyLight);

                    if (!lightBfSet.Contains(coordinates))
                    {
                        lightBfsQueue.Enqueue(coordinates);
                        lightBfSet.Add(coordinates);
                    }
                }
            }
        }
Exemple #8
0
        public ChunkMeshBuilder(ChunkSection _c)
        {
            c = _c;

            lightPos           = new List <Vector3>();
            WorldMeshBuilder   = new MeshBuilder();
            FluidMeshBuilder   = new MeshBuilder();
            FoliageMeshBuilder = new MeshBuilder();
        }
Exemple #9
0
        protected virtual void GenerateChunk(ChunkSection chunk, int x, int z)
        {
            var stone = ResourceStore.Blocks["stone"];

            for (int y = 0; y < ChunkSection.Size; y++)
            {
                var pos = new Coord3(x, y, z);
                SetBlock(chunk, pos, stone);
            }
        }
Exemple #10
0
        protected Block SetBlock(ChunkSection chunk, Coord3 localPos, BlockData data, bool replace = false)
        {
            var b = chunk.blocks.GetBlock(localPos);

            if (replace || b == null)
            {
                Block outb;
                chunk.blocks.PlaceBlock(localPos, data, out outb, false);
                return(outb);
            }
            return(null);
        }
Exemple #11
0
        public ChunkSection GetSection(int y)
        {
            var section = Sections[y >> 4];

            if (section == null)
            {
                var storage = new ChunkSection(this, y, true, 2);
                Sections[y >> 4] = storage;
                return(storage);
            }

            return((ChunkSection)section);
        }
Exemple #12
0
        public static byte GetSkyLight(BlockCoordinates blockCoordinates, ChunkSection chunk)
        {
            if (chunk == null)
            {
                return(15);
            }

            int bx = blockCoordinates.X & 0x0f;
            int by = blockCoordinates.Y & 0xff;
            int bz = blockCoordinates.Z & 0x0f;

            return(chunk.GetSkylight(bx, by - 16 * (by >> 4), bz));
        }
Exemple #13
0
        private ChunkSection GetSection(int y)
        {
            var section = Sections[y >> 4];

            if (section == null)
            {
                var storage = new ChunkSection(y, true);
                Sections[y >> 4] = storage;
                return(storage);
            }

            return((ChunkSection)section);
        }
    public void SetTile(int x, int y, int z, Tile tile)
    {
        try
        {
            var section = Sections[y >> 4] ?? (Sections[y >> 4] = new ChunkSection());

            section.Tiles[x & 15, y & 15, z & 15] = tile;
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }
Exemple #15
0
        protected override void GenerateChunk(ChunkSection chunk, int x, int z)
        {
            var localPos = new Coord3(x, 0, z);

            void Set(BlockData block)
            {
                SetBlock(chunk, localPos, block);
            }

            for (localPos.y = 0; localPos.y < ChunkSection.Size; localPos.y++)
            {
                var  pos    = localPos.BlockToWorld(chunk.worldPosition);
                bool isCave = GetChance(pos.x, pos.y, pos.z, caveFrequency, caveDensity) ||
                              GetChance(pos.y, pos.z, pos.x, caveFrequency + 0.02f, caveDensity + 4) ||
                              GetChance(-pos.x, -pos.y, -pos.z, caveFrequency - 0.02f, caveDensity - 4);

                if (pos.y <= stoneHeight && !isCave)
                {
                    for (int i = 0; i < ores.Length; i++)
                    {
                        if (GetChance(pos.x, pos.y, pos.z, ores[i].spawnFrequency, ores[i].spawnDensity))
                        {
                            Set(ores[i]);
                        }
                        else
                        {
                            Set(stone);
                        }
                    }
                }
                else if (pos.y < stoneHeight + dirtHeight && !isCave)
                {
                    Set(dirt);
                }
                else if (pos.y == stoneHeight + dirtHeight && !isCave)
                {
                    if (GetChance(pos.x, 0, pos.z, treeFrequency, treeDensity))
                    {
                        GenerateStructure(chunk, localPos.x, localPos.y + 1, localPos.z, "tree");
                    }
                    else if (GetChance(pos.x, 0, pos.z, grassFrequency, grassDensity))
                    {
                        SetBlock(chunk, new Coord3(localPos.x, localPos.y + 1, localPos.z), grass_decal);
                    }
                    Set(grass);
                }
            }
        }
Exemple #16
0
        internal IEnumerable <BlockFace> CheckNeighbors(ChunkSection section, int y, IWorld world)
        {
            List <BlockFace> faces = new List <BlockFace>();

            var sectionUp = (ChunkSection)Sections[y + 1];

            if (sectionUp != null && sectionUp.IsFaceSolid(BlockFace.Down))
            {
                faces.Add(BlockFace.Up);
            }

            var sectionDown = (ChunkSection)Sections[y - 1];

            if (sectionDown != null && sectionDown.IsFaceSolid(BlockFace.Up))
            {
                faces.Add(BlockFace.Down);
            }

            var eastChunk = world.GetChunkColumn(X + 1, Z);

            if (eastChunk != null && eastChunk.IsWallSectionSolid(3, y))
            {
                faces.Add(BlockFace.East);
            }

            var westChunk = world.GetChunkColumn(X - 1, Z);

            if (westChunk != null && westChunk.IsWallSectionSolid(2, y))
            {
                faces.Add(BlockFace.West);
            }

            var northChunk = world.GetChunkColumn(X, Z + 1);

            if (northChunk != null && northChunk.IsWallSectionSolid(5, y))
            {
                faces.Add(BlockFace.North);
            }

            var southChunk = world.GetChunkColumn(X, Z - 1);

            if (southChunk != null && southChunk.IsWallSectionSolid(4, y))
            {
                faces.Add(BlockFace.South);
            }

            return(faces);
        }
Exemple #17
0
        protected void FillWithAir(ChunkSection chunk)
        {
            var air = ResourceStore.Blocks["air"];

            for (int x = 0; x < ChunkSection.Size; x++)
            {
                for (int y = 0; y < ChunkSection.Size; y++)
                {
                    for (int z = 0; z < ChunkSection.Size; z++)
                    {
                        var pos = new Coord3(x, y, z);
                        SetBlock(chunk, pos, air);
                    }
                }
            }
        }
Exemple #18
0
    public Chunk(int x, int z)
    {
        X = x;
        Z = z;

        Heightmaps = new();
        Heightmaps.Add(HeightmapType.MotionBlocking, new Heightmap(HeightmapType.MotionBlocking, this));
        Heightmaps.Add(HeightmapType.OceanFloor, new Heightmap(HeightmapType.OceanFloor, this));
        Heightmaps.Add(HeightmapType.WorldSurface, new Heightmap(HeightmapType.WorldSurface, this));

        Sections = new ChunkSection[24];
        for (int i = 0; i < Sections.Length; i++)
        {
            Sections[i] = new ChunkSection(4, yBase: i - 4);
        }
    }
Exemple #19
0
        private static bool IsTransparent(BlockCoordinates blockCoordinates, ChunkSection section)
        {
            if (section == null)
            {
                return(true);
            }

            int bx = blockCoordinates.X & 0x0f;
            int by = blockCoordinates.Y & 0xff;
            int bz = blockCoordinates.Z & 0x0f;

            var state = section.Get(bx, by - 16 * (by >> 4), bz);

            return(state.Block is Air || state.Block.Transparent);
            //	return bid == 0 || BlockFactory.TransparentBlocks[bid] == 1;
        }
Exemple #20
0
        public void SetBlockId(Int3 location, BlockId state)
        {
            Int3 chunkSectionKey = location.GetChunk();
            Int2 chunkKey        = new Int2(chunkSectionKey.X, chunkSectionKey.Z);

            if (Chunks.ContainsKey(chunkKey))
            {
                ChunkColumn  chunk   = Chunks[chunkKey];
                ChunkSection section = chunk[chunkSectionKey.Y];
                if (section != null)
                {
                    Int3 bp = location.InChunkBlock();
                    section.SetState(bp.X, bp.Y, bp.Z, state);
                }
            }
        }
Exemple #21
0
        public static int GetDiffuseLevel(BlockCoordinates blockCoordinates, ChunkSection section)
        {
            //TODO: Figure out if this is really correct. Perhaps should be zero.
            if (section == null)
            {
                return(15);
            }

            int bx = blockCoordinates.X & 0x0f;
            int by = blockCoordinates.Y & 0xff;
            int bz = blockCoordinates.Z & 0x0f;

            var state = section.Get(bx, by - 16 * (by >> 4), bz);

            return(state.Block.LightOpacity);
            //return bid == 8 || bid == 9 ? 3 : bid == 18 || bid == 161 || bid == 30 ? 2 : 1;
        }
Exemple #22
0
        protected void GenerateStructure(ChunkSection chunk, int x, int y, int z, string id)
        {
            var s = ResourceStore.Structures[id];

            for (int l = 0; l < s.height; l++)
            {
                for (int xi = 0; xi < s.size.x; xi++)
                {
                    for (int zi = 0; zi < s.size.y; zi++)
                    {
                        var val   = s[l][zi * s.size.y + xi];
                        var block = s.blocks[val];
                        var pos   = new Coord3(x + xi, y + l, z + zi) - (Coord3)s.origin + Coord3.one;
                        SetBlock(chunk, pos, block, s.cutout);
                    }
                }
            }
        }
Exemple #23
0
    public Chunk Clone(int x, int z)
    {
        var sections = new ChunkSection[Sections.Length];

        for (int i = 0; i < sections.Length; i++)
        {
            sections[i] = Sections[i].Clone();
        }

        var heightmaps = new Dictionary <HeightmapType, Heightmap>();

        var chunk = new Chunk(x, z, sections, heightmaps, isGenerated);

        foreach (var(type, heightmap) in Heightmaps)
        {
            heightmaps.Add(type, heightmap.Clone(chunk));
        }

        return(chunk);
    }
Exemple #24
0
        protected override void GenerateChunk(ChunkSection chunk, int x, int z)
        {
            if (chunk.position != Coord3.zero)
            {
                return;
            }

            for (int y = 0; y < 4; y++)
            {
                if (x >= 0 && x <= 3 && z >= 0 && z <= 3)
                {
                    var pos   = new Coord3(8 * x, 8 * y, 8 * z);
                    var block = SetBlock(chunk, pos, proto);
                    chunk.blocks.GetCustomBlock <RotatedBlock>(block, b => b.SetRotation(new Coord3(x, y, z)));

                    var block_obj = SetBlock(chunk, new Coord3(8 * x, 8 * y + 2, 8 * z), proto_obj);
                    chunk.blocks.GetCustomBlock <StandaloneBlock>(block_obj, b => b.gameObject.transform.localEulerAngles = new Vector3(x * 90, y * 90, z * 90));
                }
            }
        }
Exemple #25
0
    public void OnBlockChangePacket(IPacket packet)
    {
        var bcp = packet as BlockChangePacket;

        if (bcp != null)
        {
            int x = bcp.Location.x;
            int y = bcp.Location.y;
            int z = bcp.Location.z;
            if (y < 0 || y >= ChunkManager.H)
            {
                return;
            }
            int cx = x >> 4;
            int cz = z >> 4;

            ChunkManager chunk = CurrentWorld.GetChunk(cx, cz, false);
            if ((object)chunk == null)
            {
                return;
            }

            int          cy      = y >> 4;
            ChunkSection section = chunk.Sections[cy];

            if ((object)section == null)
            {
                return;
            }

            int bx = x & 0x000F;
            int by = y & 0x000F;
            int bz = z & 0x000F;

            section.DecodeLongs();
            section.Data[bx, by, bz] = (short)bcp.BlockId;
            section.Rend();
        }
    }
        public static int GetChunkSectionDataLength(ChunkSection section)
        {
            if (section == null)
            {
                throw new ArgumentNullException(nameof(section));
            }

            IBlockPalette palette = section.BlockPalette;

            int length = 0;

            length += sizeof(short);
            length += sizeof(byte);
            length += palette.GetEncodedSize();

            int longCount = GetUnderlyingDataLength(UnderlyingDataSize, palette.BitsPerBlock);

            length += VarInt.GetEncodedSize(longCount);
            length += longCount * sizeof(ulong);

            return(length);
        }
    public bool IsEmptyBetween(int startY, int endY)
    {
        if (startY < 0)
        {
            startY = 0;
        }

        if (endY >= 256)
        {
            endY = 255;
        }

        for (var i = startY; i <= endY; i += 16)
        {
            if (!ChunkSection.IsEmpty(Sections[i >> 4]))
            {
                return(false);
            }
        }

        return(true);
    }
        public ChunkColumn GenerateChunkAt(Vector3 ChunkPos)
        {
            ChunkSection[] sections = new ChunkSection[Constants.SECTION_PER_COLUMN];

            for (int SectionY = 0; SectionY < Constants.SECTION_PER_COLUMN; SectionY++)
            {
                Dictionary <Vector3, Block> blocks = new Dictionary <Vector3, Block>();
                for (int y = 0; y < Constants.SECTION_HEIGHT; y++)
                {
                    for (int z = 0; z < Constants.SECTION_WIDTH; z++)
                    {
                        for (int x = 0; x < Constants.SECTION_WIDTH; x++)
                        {
                            var   ActualPos = y * SectionY;
                            Block state;
                            if (DefaultBlocks.TryGetValue((int)TypeUtils.ChunkToWorldSpace(y, SectionY), out Block block)) ///AAAAAAH Performance
                            {
                                state = block;
                            }
                            else
                            {
                                state = Block.Air;
                            }
                            //state = new BlockState(1, 0);

                            blocks.Add(new Vector3(x, y, z), state);
                        }
                    }
                }
                sections[SectionY] = new ChunkSection(blocks)
                {
                    ChunkX = ChunkPos.X,
                    ChunkY = SectionY,
                    ChunkZ = ChunkPos.Z
                };
            }
            return(new ChunkColumn(sections, ChunkPos));
        }
Exemple #29
0
        private void WriteChunkSection(ChunkSection section, ref MemoryStream stream)
        {
            #region Section
            byte bitsPerBlock = FULL_SIZE_BITS_PER_BLOCK;

            MinecraftPacket.WriteToStream(stream, bitsPerBlock);
            //0: disabled?
            //MinecraftPacket.WriteToStream(stream, (VarInt)0);   //up to 4: Blocks are encoded as 4 bits. The palette is used and sent.
            //5 to 8: Blocks are encoded with the given number of bits.The palette is used and sent.
            //9 and above: The palette is not sent. Blocks are encoded by their whole ID in the global palette, with bits per block being set as the base 2 logarithm of the number of block states, rounded up. For the current vanilla release, this is 14 bits per block.

            // A bitmask that contains bitsPerBlock set bits
            uint individualValueMask = (uint)((1 << bitsPerBlock) - 1);

            int      dataLength = (Constants.SECTION_HEIGHT * Constants.SECTION_WIDTH * Constants.SECTION_DEPTH) * bitsPerBlock / 64;
            UInt64[] DataArray  = new UInt64[dataLength];

            for (int y = 0; y < Constants.SECTION_HEIGHT; y++)
            {
                for (int z = 0; z < Constants.SECTION_WIDTH; z++)
                {
                    for (int x = 0; x < Constants.SECTION_WIDTH; x++)
                    {
                        var blockPos = new Vector3(x, y, z);

                        int blockNumber = ((((int)blockPos.Y * Constants.SECTION_HEIGHT) + (int)blockPos.Z) * Constants.SECTION_WIDTH) + (int)blockPos.X;
                        int startLong   = (blockNumber * bitsPerBlock) / 64;
                        int startOffset = (blockNumber * bitsPerBlock) % 64;
                        int endLong     = ((blockNumber + 1) * bitsPerBlock - 1) / 64;

                        Block state = section.blocks[blockPos];

                        //C# Still limits the Byte Size to uint if this is a uint,
                        //even after byteshifting into a ulong
                        ulong value = (ulong)state.GetGlobalPaletteIDFromState();
                        value &= individualValueMask;

                        DataArray[startLong] |= (value << startOffset);

                        if (startLong != endLong)
                        {
                            DataArray[endLong] = (value >> (64 - startOffset));
                        }
                    }
                }
            }

            Console.WriteLine($"Data Array was {DataArray.Length} long");
            MinecraftPacket.WriteToStream(stream, (VarInt)dataLength);
            MinecraftPacket.WriteArrayToStream(stream, DataArray);

            List <byte> BlockLightData = new List <byte>();
            for (int y = 0; y < Constants.SECTION_HEIGHT; y++)
            {
                for (int z = 0; z < Constants.SECTION_WIDTH; z++)
                {
                    for (int x = 0; x < Constants.SECTION_WIDTH; x += 2)
                    {
                        // Note: x += 2 above; we read 2 values along x each time
                        byte blocklight1 = (byte)(section.blocks[new Vector3(x, y, z)].BlockLight);
                        //this only works because 16 is even
                        byte blocklight2 = (byte)(section.blocks[new Vector3(x + 1, y, z)].BlockLight << 4);
                        byte value       = (byte)(blocklight1 | blocklight2);
                        BlockLightData.Add(value);
                    }
                }
            }
            Console.WriteLine($"BlockLight was {BlockLightData.Count} Long");
            MinecraftPacket.WriteArrayToStream(stream, BlockLightData.ToArray());

            List <byte> SkyLightData = new List <byte>();
            if (section.HasSkylight)
            { // => current dimension is overworld / 0
                for (int y = 0; y < Constants.SECTION_HEIGHT; y++)
                {
                    for (int z = 0; z < Constants.SECTION_WIDTH; z++)
                    {
                        for (int x = 0; x < Constants.SECTION_WIDTH; x += 2)
                        {
                            // Note: x += 2 above; we read 2 values along x each time
                            byte blocklight1 = (byte)(section.blocks[new Vector3(x, y, z)].SkyLight);
                            //this only works because 16 is even
                            byte blocklight2 = (byte)(section.blocks[new Vector3(x + 1, y, z)].SkyLight << 4);
                            byte value       = (byte)(blocklight1 | blocklight2);
                            SkyLightData.Add(value);
                        }
                    }
                }
            }
            Console.WriteLine($"SkyLight was {SkyLightData.Count} Long");
            MinecraftPacket.WriteArrayToStream(stream, SkyLightData.ToArray());
            #endregion
        }
Exemple #30
0
        private byte SetLightLevel(IBlockAccess level, ChunkColumn chunk, ChunkSection subChunk, int sectionIdx, Queue <BlockCoordinates> lightBfsQueue, HashSet <BlockCoordinates> lightBfSet, BlockCoordinates coordinates, byte lightLevel, bool down = false, bool up = false)
        {
            //Interlocked.Add(ref visits, 1);

            if (TrackResults)
            {
                MakeVisit(coordinates);
            }

            var chunkCoords = (ChunkCoordinates)coordinates;

            if (!(up || down) && (chunk.X != coordinates.X >> 4 || chunk.Z != coordinates.Z >> 4))
            {
                chunk    = (ChunkColumn)level.GetChunk(chunkCoords);
                subChunk = null;
            }
            else
            {
                if ((up || down) && coordinates.Y >> 4 != sectionIdx)
                {
                    subChunk = null;
                }
            }

            if (chunk == null /* || chunk.chunks == null*/)
            {
                return(lightLevel);
            }

            if (!down && !up && coordinates.Y >= GetHeight(coordinates, chunk))
            {
                if (GetSkyLight(coordinates, subChunk) != 15)
                {
                    SetSkyLight(coordinates, 15, chunk);

                    if (!lightBfSet.Contains(coordinates))
                    {
                        lightBfsQueue.Enqueue(coordinates);
                        lightBfSet.Add(coordinates);
                    }
                }

                return(15);
            }

            if (subChunk == null)
            {
                subChunk = (ChunkSection)chunk.Sections[coordinates.Y >> 4];
            }

            bool isTransparent = IsTransparent(coordinates, subChunk);
            byte skyLight      = GetSkyLight(coordinates, subChunk);

            if (down && isTransparent && lightLevel == 15)
            {
                if (IsNotBlockingSkylight(coordinates, chunk))
                {
                    if (skyLight != 15)
                    {
                        SetSkyLight(coordinates, 15, chunk);
                    }

                    if (!lightBfSet.Contains(coordinates))
                    {
                        lightBfsQueue.Enqueue(coordinates);
                        lightBfSet.Add(coordinates);
                    }

                    return(15);
                }
            }

            if (isTransparent)
            {
                int diffuseLevel = GetDiffuseLevel(coordinates, subChunk);
                if (skyLight + 1 + diffuseLevel <= lightLevel)
                {
                    byte newLevel = (byte)(lightLevel - diffuseLevel);
                    SetSkyLight(coordinates, newLevel, chunk);

                    if (!lightBfSet.Contains(coordinates))
                    {
                        lightBfsQueue.Enqueue(coordinates);
                        lightBfSet.Add(coordinates);
                    }

                    return(newLevel);
                }
            }

            return(skyLight);
        }