001    package net.minecraft.src;
002    
003    import cpw.mods.fml.common.Side;
004    import cpw.mods.fml.common.asm.SideOnly;
005    import java.util.ArrayList;
006    import java.util.Arrays;
007    import java.util.HashMap;
008    import java.util.Iterator;
009    import java.util.List;
010    import java.util.Map;
011    import java.util.Random;
012    
013    import net.minecraftforge.common.MinecraftForge;
014    import net.minecraftforge.event.entity.EntityEvent;
015    import net.minecraftforge.event.world.ChunkEvent;
016    
017    public class Chunk
018    {
019        /**
020         * Determines if the chunk is lit or not at a light value greater than 0.
021         */
022        public static boolean isLit;
023    
024        /**
025         * Used to store block IDs, block MSBs, Sky-light maps, Block-light maps, and metadata. Each entry corresponds to a
026         * logical segment of 16x16x16 blocks, stacked vertically.
027         */
028        private ExtendedBlockStorage[] storageArrays;
029    
030        /**
031         * Contains a 16x16 mapping on the X/Z plane of the biome ID to which each colum belongs.
032         */
033        private byte[] blockBiomeArray;
034    
035        /**
036         * A map, similar to heightMap, that tracks how far down precipitation can fall.
037         */
038        public int[] precipitationHeightMap;
039    
040        /** Which columns need their skylightMaps updated. */
041        public boolean[] updateSkylightColumns;
042    
043        /** Whether or not this Chunk is currently loaded into the World */
044        public boolean isChunkLoaded;
045    
046        /** Reference to the World object. */
047        public World worldObj;
048        public int[] heightMap;
049    
050        /** The x coordinate of the chunk. */
051        public final int xPosition;
052    
053        /** The z coordinate of the chunk. */
054        public final int zPosition;
055        private boolean isGapLightingUpdated;
056    
057        /** A Map of ChunkPositions to TileEntities in this chunk */
058        public Map chunkTileEntityMap;
059    
060        /**
061         * Array of Lists containing the entities in this Chunk. Each List represents a 16 block subchunk.
062         */
063        public List[] entityLists;
064    
065        /** Boolean value indicating if the terrain is populated. */
066        public boolean isTerrainPopulated;
067    
068        /**
069         * Set to true if the chunk has been modified and needs to be updated internally.
070         */
071        public boolean isModified;
072    
073        /**
074         * Whether this Chunk has any Entities and thus requires saving on every tick
075         */
076        public boolean hasEntities;
077    
078        /** The time according to World.worldTime when this chunk was last saved */
079        public long lastSaveTime;
080        public boolean deferRender;
081        public int field_82912_p;
082    
083        /**
084         * Contains the current round-robin relight check index, and is implied as the relight check location as well.
085         */
086        private int queuedLightChecks;
087        boolean field_76653_p;
088    
089        public Chunk(World par1World, int par2, int par3)
090        {
091            this.storageArrays = new ExtendedBlockStorage[16];
092            this.blockBiomeArray = new byte[256];
093            this.precipitationHeightMap = new int[256];
094            this.updateSkylightColumns = new boolean[256];
095            this.isGapLightingUpdated = false;
096            this.chunkTileEntityMap = new HashMap();
097            this.isTerrainPopulated = false;
098            this.isModified = false;
099            this.hasEntities = false;
100            this.lastSaveTime = 0L;
101            this.deferRender = false;
102            this.field_82912_p = 0;
103            this.queuedLightChecks = 4096;
104            this.field_76653_p = false;
105            this.entityLists = new List[16];
106            this.worldObj = par1World;
107            this.xPosition = par2;
108            this.zPosition = par3;
109            this.heightMap = new int[256];
110    
111            for (int var4 = 0; var4 < this.entityLists.length; ++var4)
112            {
113                this.entityLists[var4] = new ArrayList();
114            }
115    
116            Arrays.fill(this.precipitationHeightMap, -999);
117            Arrays.fill(this.blockBiomeArray, (byte) - 1);
118        }
119    
120        public Chunk(World par1World, byte[] par2ArrayOfByte, int par3, int par4)
121        {
122            this(par1World, par3, par4);
123            int var5 = par2ArrayOfByte.length / 256;
124    
125            for (int var6 = 0; var6 < 16; ++var6)
126            {
127                for (int var7 = 0; var7 < 16; ++var7)
128                {
129                    for (int var8 = 0; var8 < var5; ++var8)
130                    {
131                        /* FORGE: The following change, a cast from unsigned byte to int,
132                         * fixes a vanilla bug when generating new chunks that contain a block ID > 127 */
133                        int var9 = par2ArrayOfByte[var6 << 11 | var7 << 7 | var8] & 0xFF;
134    
135                        if (var9 != 0)
136                        {
137                            int var10 = var8 >> 4;
138    
139                            if (this.storageArrays[var10] == null)
140                            {
141                                this.storageArrays[var10] = new ExtendedBlockStorage(var10 << 4);
142                            }
143    
144                            this.storageArrays[var10].setExtBlockID(var6, var8 & 15, var7, var9);
145                        }
146                    }
147                }
148            }
149        }
150    
151        /**
152         * Metadata sensitive Chunk constructor for use in new ChunkProviders that
153         * use metadata sensitive blocks during generation.
154         *
155         * @param world The world this chunk belongs to
156         * @param ids A ByteArray containing all the BlockID's to set this chunk to
157         * @param metadata A ByteArray containing all the metadata to set this chunk to
158         * @param chunkX The chunk's X position
159         * @param chunkZ The Chunk's Z position
160         */
161        public Chunk(World world, byte[] ids, byte[] metadata, int chunkX, int chunkZ)
162        {
163            this(world, chunkX, chunkZ);
164            int var5 = ids.length / 256;
165    
166            for (int x = 0; x < 16; ++x)
167            {
168                for (int z = 0; z < 16; ++z)
169                {
170                    for (int y = 0; y < var5; ++y)
171                    {
172                        int idx = x << 11 | z << 7 | y;
173                       int id = ids[idx] & 0xFF;
174                        int meta = metadata[idx];
175    
176                        if (id != 0)
177                        {
178                            int var10 = y >> 4;
179    
180                            if (this.storageArrays[var10] == null)
181                            {
182                                this.storageArrays[var10] = new ExtendedBlockStorage(var10 << 4);
183                            }
184    
185                            this.storageArrays[var10].setExtBlockID(x, y & 15, z, id);
186                            this.storageArrays[var10].setExtBlockMetadata(x, y & 15, z, meta);
187                        }
188                    }
189                }
190            }
191        }
192    
193        /**
194         * A Chunk Constructor which handles shorts to allow block ids > 256 (full 4096 range)
195         * Meta data sensitive
196         * NOTE: The x,y,z order of the array is different from the native Chunk constructor to allow for generation > y127
197         * NOTE: This is possibly more efficient than the standard constructor due to less memory skipping
198         *
199         * @param world The world this chunk belongs to
200         * @param ids A ShortArray containing all the BlockID's to set this chunk to (x is low order, z is mid, y is high)
201         * @param metadata A ByteArray containing all the metadata to set this chunk to
202         * @param chunkX The chunk's X position
203         * @param chunkZ The Chunk's Z position
204         */
205        public Chunk(World world, short[] ids, byte[] metadata, int chunkX, int chunkZ)
206        {
207            this(world, chunkX, chunkZ);
208            int max = ids.length / 256;
209    
210            for (int y = 0; y < max; ++y)
211            {
212                for (int z = 0; z < 16; ++z)
213                {
214                    for (int x = 0; x < 16; ++x)
215                    {
216                        int idx = y << 8 | z << 4 | x;
217                        int id = ids[idx] & 0xFFFFFF;
218                        int meta = metadata[idx];
219    
220                        if (id != 0) {
221                            int storageBlock = y >> 4;
222    
223                            if (this.storageArrays[storageBlock] == null) {
224                                    this.storageArrays[storageBlock] = new ExtendedBlockStorage(storageBlock << 4);
225                            }
226            
227                            this.storageArrays[storageBlock].setExtBlockID(x, y & 15, z, id);
228                            this.storageArrays[storageBlock].setExtBlockMetadata(x, y & 15, z, meta);
229                        }
230                    }
231                }
232            }
233        }
234    
235        /**
236         * Checks whether the chunk is at the X/Z location specified
237         */
238        public boolean isAtLocation(int par1, int par2)
239        {
240            return par1 == this.xPosition && par2 == this.zPosition;
241        }
242    
243        /**
244         * Returns the value in the height map at this x, z coordinate in the chunk
245         */
246        public int getHeightValue(int par1, int par2)
247        {
248            return this.heightMap[par2 << 4 | par1];
249        }
250    
251        /**
252         * Returns the topmost ExtendedBlockStorage instance for this Chunk that actually contains a block.
253         */
254        public int getTopFilledSegment()
255        {
256            for (int var1 = this.storageArrays.length - 1; var1 >= 0; --var1)
257            {
258                if (this.storageArrays[var1] != null)
259                {
260                    return this.storageArrays[var1].getYLocation();
261                }
262            }
263    
264            return 0;
265        }
266    
267        /**
268         * Returns the ExtendedBlockStorage array for this Chunk.
269         */
270        public ExtendedBlockStorage[] getBlockStorageArray()
271        {
272            return this.storageArrays;
273        }
274    
275        @SideOnly(Side.CLIENT)
276    
277        /**
278         * Generates the height map for a chunk from scratch
279         */
280        public void generateHeightMap()
281        {
282            int var1 = this.getTopFilledSegment();
283    
284            for (int var2 = 0; var2 < 16; ++var2)
285            {
286                int var3 = 0;
287    
288                while (var3 < 16)
289                {
290                    this.precipitationHeightMap[var2 + (var3 << 4)] = -999;
291                    int var4 = var1 + 16 - 1;
292    
293                    while (true)
294                    {
295                        if (var4 > 0)
296                        {
297                            int var5 = this.getBlockID(var2, var4 - 1, var3);
298    
299                            if (getBlockLightOpacity(var2, var4 - 1, var3) == 0)
300                            {
301                                --var4;
302                                continue;
303                            }
304    
305                            this.heightMap[var3 << 4 | var2] = var4;
306                        }
307    
308                        ++var3;
309                        break;
310                    }
311                }
312            }
313    
314            this.isModified = true;
315        }
316    
317        /**
318         * Generates the initial skylight map for the chunk upon generation or load.
319         */
320        public void generateSkylightMap()
321        {
322            int var1 = this.getTopFilledSegment();
323            this.field_82912_p = Integer.MAX_VALUE;
324            int var2;
325            int var3;
326    
327            for (var2 = 0; var2 < 16; ++var2)
328            {
329                var3 = 0;
330    
331                while (var3 < 16)
332                {
333                    this.precipitationHeightMap[var2 + (var3 << 4)] = -999;
334                    int var4 = var1 + 16 - 1;
335    
336                    while (true)
337                    {
338                        if (var4 > 0)
339                        {
340                            if (this.getBlockLightOpacity(var2, var4 - 1, var3) == 0)
341                            {
342                                --var4;
343                                continue;
344                            }
345    
346                            this.heightMap[var3 << 4 | var2] = var4;
347    
348                            if (var4 < this.field_82912_p)
349                            {
350                                this.field_82912_p = var4;
351                            }
352                        }
353    
354                        if (!this.worldObj.provider.hasNoSky)
355                        {
356                            var4 = 15;
357                            int var5 = var1 + 16 - 1;
358    
359                            do
360                            {
361                                var4 -= this.getBlockLightOpacity(var2, var5, var3);
362    
363                                if (var4 > 0)
364                                {
365                                    ExtendedBlockStorage var6 = this.storageArrays[var5 >> 4];
366    
367                                    if (var6 != null)
368                                    {
369                                        var6.setExtSkylightValue(var2, var5 & 15, var3, var4);
370                                        this.worldObj.markBlockForRenderUpdate((this.xPosition << 4) + var2, var5, (this.zPosition << 4) + var3);
371                                    }
372                                }
373    
374                                --var5;
375                            }
376                            while (var5 > 0 && var4 > 0);
377                        }
378    
379                        ++var3;
380                        break;
381                    }
382                }
383            }
384    
385            this.isModified = true;
386    
387            for (var2 = 0; var2 < 16; ++var2)
388            {
389                for (var3 = 0; var3 < 16; ++var3)
390                {
391                    this.propagateSkylightOcclusion(var2, var3);
392                }
393            }
394        }
395    
396        /**
397         * Propagates a given sky-visible block's light value downward and upward to neighboring blocks as necessary.
398         */
399        private void propagateSkylightOcclusion(int par1, int par2)
400        {
401            this.updateSkylightColumns[par1 + par2 * 16] = true;
402            this.isGapLightingUpdated = true;
403        }
404    
405        /**
406         * Runs delayed skylight updates.
407         */
408        private void updateSkylight_do()
409        {
410            this.worldObj.theProfiler.startSection("recheckGaps");
411    
412            if (this.worldObj.doChunksNearChunkExist(this.xPosition * 16 + 8, 0, this.zPosition * 16 + 8, 16))
413            {
414                for (int var1 = 0; var1 < 16; ++var1)
415                {
416                    for (int var2 = 0; var2 < 16; ++var2)
417                    {
418                        if (this.updateSkylightColumns[var1 + var2 * 16])
419                        {
420                            this.updateSkylightColumns[var1 + var2 * 16] = false;
421                            int var3 = this.getHeightValue(var1, var2);
422                            int var4 = this.xPosition * 16 + var1;
423                            int var5 = this.zPosition * 16 + var2;
424                            int var6 = this.worldObj.func_82734_g(var4 - 1, var5);
425                            int var7 = this.worldObj.func_82734_g(var4 + 1, var5);
426                            int var8 = this.worldObj.func_82734_g(var4, var5 - 1);
427                            int var9 = this.worldObj.func_82734_g(var4, var5 + 1);
428    
429                            if (var7 < var6)
430                            {
431                                var6 = var7;
432                            }
433    
434                            if (var8 < var6)
435                            {
436                                var6 = var8;
437                            }
438    
439                            if (var9 < var6)
440                            {
441                                var6 = var9;
442                            }
443    
444                            this.checkSkylightNeighborHeight(var4, var5, var6);
445                            this.checkSkylightNeighborHeight(var4 - 1, var5, var3);
446                            this.checkSkylightNeighborHeight(var4 + 1, var5, var3);
447                            this.checkSkylightNeighborHeight(var4, var5 - 1, var3);
448                            this.checkSkylightNeighborHeight(var4, var5 + 1, var3);
449                        }
450                    }
451                }
452    
453                this.isGapLightingUpdated = false;
454            }
455    
456            this.worldObj.theProfiler.endSection();
457        }
458    
459        /**
460         * Checks the height of a block next to a sky-visible block and schedules a lighting update as necessary.
461         */
462        private void checkSkylightNeighborHeight(int par1, int par2, int par3)
463        {
464            int var4 = this.worldObj.getHeightValue(par1, par2);
465    
466            if (var4 > par3)
467            {
468                this.updateSkylightNeighborHeight(par1, par2, par3, var4 + 1);
469            }
470            else if (var4 < par3)
471            {
472                this.updateSkylightNeighborHeight(par1, par2, var4, par3 + 1);
473            }
474        }
475    
476        private void updateSkylightNeighborHeight(int par1, int par2, int par3, int par4)
477        {
478            if (par4 > par3 && this.worldObj.doChunksNearChunkExist(par1, 0, par2, 16))
479            {
480                for (int var5 = par3; var5 < par4; ++var5)
481                {
482                    this.worldObj.updateLightByType(EnumSkyBlock.Sky, par1, var5, par2);
483                }
484    
485                this.isModified = true;
486            }
487        }
488    
489        /**
490         * Initiates the recalculation of both the block-light and sky-light for a given block inside a chunk.
491         */
492        private void relightBlock(int par1, int par2, int par3)
493        {
494            int var4 = this.heightMap[par3 << 4 | par1] & 255;
495            int var5 = var4;
496    
497            if (par2 > var4)
498            {
499                var5 = par2;
500            }
501    
502            while (var5 > 0 && this.getBlockLightOpacity(par1, var5 - 1, par3) == 0)
503            {
504                --var5;
505            }
506    
507            if (var5 != var4)
508            {
509                this.worldObj.markBlocksDirtyVertical(par1 + this.xPosition * 16, par3 + this.zPosition * 16, var5, var4);
510                this.heightMap[par3 << 4 | par1] = var5;
511                int var6 = this.xPosition * 16 + par1;
512                int var7 = this.zPosition * 16 + par3;
513                int var8;
514                int var12;
515    
516                if (!this.worldObj.provider.hasNoSky)
517                {
518                    ExtendedBlockStorage var9;
519    
520                    if (var5 < var4)
521                    {
522                        for (var8 = var5; var8 < var4; ++var8)
523                        {
524                            var9 = this.storageArrays[var8 >> 4];
525    
526                            if (var9 != null)
527                            {
528                                var9.setExtSkylightValue(par1, var8 & 15, par3, 15);
529                                this.worldObj.markBlockForRenderUpdate((this.xPosition << 4) + par1, var8, (this.zPosition << 4) + par3);
530                            }
531                        }
532                    }
533                    else
534                    {
535                        for (var8 = var4; var8 < var5; ++var8)
536                        {
537                            var9 = this.storageArrays[var8 >> 4];
538    
539                            if (var9 != null)
540                            {
541                                var9.setExtSkylightValue(par1, var8 & 15, par3, 0);
542                                this.worldObj.markBlockForRenderUpdate((this.xPosition << 4) + par1, var8, (this.zPosition << 4) + par3);
543                            }
544                        }
545                    }
546    
547                    var8 = 15;
548    
549                    while (var5 > 0 && var8 > 0)
550                    {
551                        --var5;
552                        var12 = this.getBlockLightOpacity(par1, var5, par3);
553    
554                        if (var12 == 0)
555                        {
556                            var12 = 1;
557                        }
558    
559                        var8 -= var12;
560    
561                        if (var8 < 0)
562                        {
563                            var8 = 0;
564                        }
565    
566                        ExtendedBlockStorage var10 = this.storageArrays[var5 >> 4];
567    
568                        if (var10 != null)
569                        {
570                            var10.setExtSkylightValue(par1, var5 & 15, par3, var8);
571                        }
572                    }
573                }
574    
575                var8 = this.heightMap[par3 << 4 | par1];
576                var12 = var4;
577                int var13 = var8;
578    
579                if (var8 < var4)
580                {
581                    var12 = var8;
582                    var13 = var4;
583                }
584    
585                if (var8 < this.field_82912_p)
586                {
587                    this.field_82912_p = var8;
588                }
589    
590                if (!this.worldObj.provider.hasNoSky)
591                {
592                    this.updateSkylightNeighborHeight(var6 - 1, var7, var12, var13);
593                    this.updateSkylightNeighborHeight(var6 + 1, var7, var12, var13);
594                    this.updateSkylightNeighborHeight(var6, var7 - 1, var12, var13);
595                    this.updateSkylightNeighborHeight(var6, var7 + 1, var12, var13);
596                    this.updateSkylightNeighborHeight(var6, var7, var12, var13);
597                }
598    
599                this.isModified = true;
600            }
601        }
602    
603        public int getBlockLightOpacity(int par1, int par2, int par3)
604        {
605            int x = (xPosition << 4) + par1;
606            int z = (zPosition << 4) + par3;
607            Block block = Block.blocksList[getBlockID(par1, par2, par3)];
608            return (block == null ? 0 : block.getLightOpacity(worldObj, x, par2, z));
609        }
610    
611        /**
612         * Return the ID of a block in the chunk.
613         */
614        public int getBlockID(int par1, int par2, int par3)
615        {
616            if (par2 >> 4 >= this.storageArrays.length || par2 >> 4 < 0)
617            {
618                return 0;
619            }
620            else
621            {
622                ExtendedBlockStorage var4 = this.storageArrays[par2 >> 4];
623                return var4 != null ? var4.getExtBlockID(par1, par2 & 15, par3) : 0;
624            }
625        }
626    
627        /**
628         * Return the metadata corresponding to the given coordinates inside a chunk.
629         */
630        public int getBlockMetadata(int par1, int par2, int par3)
631        {
632            if (par2 >> 4 >= this.storageArrays.length || par2 >> 4 < 0)
633            {
634                return 0;
635            }
636            else
637            {
638                ExtendedBlockStorage var4 = this.storageArrays[par2 >> 4];
639                return var4 != null ? var4.getExtBlockMetadata(par1, par2 & 15, par3) : 0;
640            }
641        }
642    
643        /**
644         * Sets a blockID for a position in the chunk. Args: x, y, z, blockID
645         */
646        public boolean setBlockID(int par1, int par2, int par3, int par4)
647        {
648            return this.setBlockIDWithMetadata(par1, par2, par3, par4, 0);
649        }
650    
651        /**
652         * Sets a blockID of a position within a chunk with metadata. Args: x, y, z, blockID, metadata
653         */
654        public boolean setBlockIDWithMetadata(int par1, int par2, int par3, int par4, int par5)
655        {
656            int var6 = par3 << 4 | par1;
657    
658            if (par2 >= this.precipitationHeightMap[var6] - 1)
659            {
660                this.precipitationHeightMap[var6] = -999;
661            }
662    
663            int var7 = this.heightMap[var6];
664            int var8 = this.getBlockID(par1, par2, par3);
665            int var9 = this.getBlockMetadata(par1, par2, par3);
666    
667            if (var8 == par4 && var9 == par5)
668            {
669                return false;
670            }
671            else
672            {
673                if (par2 >> 4 >= storageArrays.length || par2 >> 4 < 0)
674                {
675                    return false;
676                }
677    
678                ExtendedBlockStorage var10 = this.storageArrays[par2 >> 4];
679                boolean var11 = false;
680    
681                if (var10 == null)
682                {
683                    if (par4 == 0)
684                    {
685                        return false;
686                    }
687    
688                    var10 = this.storageArrays[par2 >> 4] = new ExtendedBlockStorage(par2 >> 4 << 4);
689                    var11 = par2 >= var7;
690                }
691    
692                int var12 = this.xPosition * 16 + par1;
693                int var13 = this.zPosition * 16 + par3;
694    
695                if (var8 != 0 && !this.worldObj.isRemote)
696                {
697                    Block.blocksList[var8].onSetBlockIDWithMetaData(this.worldObj, var12, par2, var13, var9);
698                }
699    
700                var10.setExtBlockID(par1, par2 & 15, par3, par4);
701    
702                if (var8 != 0)
703                {
704                    if (!this.worldObj.isRemote)
705                    {
706                        Block.blocksList[var8].breakBlock(this.worldObj, var12, par2, var13, var8, var9);
707                    }
708                    else if (Block.blocksList[var8] != null && Block.blocksList[var8].hasTileEntity(var9))
709                    {
710                        TileEntity te = worldObj.getBlockTileEntity(var12, par2, var13);
711                        if (te != null && te.shouldRefresh(var8, par4, var9, par5, worldObj, var12, par2, var13))
712                        {
713                            this.worldObj.removeBlockTileEntity(var12, par2, var13);
714                        }
715                    }
716                }
717    
718                if (var10.getExtBlockID(par1, par2 & 15, par3) != par4)
719                {
720                    return false;
721                }
722                else
723                {
724                    var10.setExtBlockMetadata(par1, par2 & 15, par3, par5);
725    
726                    if (var11)
727                    {
728                        this.generateSkylightMap();
729                    }
730                    else
731                    {
732                        if (getBlockLightOpacity(par1, par2, par3) > 0)
733                        {
734                            if (par2 >= var7)
735                            {
736                                this.relightBlock(par1, par2 + 1, par3);
737                            }
738                        }
739                        else if (par2 == var7 - 1)
740                        {
741                            this.relightBlock(par1, par2, par3);
742                        }
743    
744                        this.propagateSkylightOcclusion(par1, par3);
745                    }
746    
747                    TileEntity var14;
748    
749                    if (par4 != 0)
750                    {
751                        if (!this.worldObj.isRemote)
752                        {
753                            Block.blocksList[par4].onBlockAdded(this.worldObj, var12, par2, var13);
754                        }
755    
756                        if (Block.blocksList[par4] != null && Block.blocksList[par4].hasTileEntity(par5))
757                        {
758                            var14 = this.getChunkBlockTileEntity(par1, par2, par3);
759    
760                            if (var14 == null)
761                            {
762                                var14 = Block.blocksList[par4].createTileEntity(this.worldObj, par5);
763                                this.worldObj.setBlockTileEntity(var12, par2, var13, var14);
764                            }
765    
766                            if (var14 != null)
767                            {
768                                var14.updateContainingBlockInfo();
769                                var14.blockMetadata = par5;
770                            }
771                        }
772                    }
773    
774                    this.isModified = true;
775                    return true;
776                }
777            }
778        }
779    
780        /**
781         * Set the metadata of a block in the chunk
782         */
783        public boolean setBlockMetadata(int par1, int par2, int par3, int par4)
784        {
785            ExtendedBlockStorage var5 = (par2 >> 4 >= storageArrays.length || par2 >> 4 < 0 ? null : storageArrays[par2 >> 4]);
786    
787            if (var5 == null)
788            {
789                return false;
790            }
791            else
792            {
793                int var6 = var5.getExtBlockMetadata(par1, par2 & 15, par3);
794    
795                if (var6 == par4)
796                {
797                    return false;
798                }
799                else
800                {
801                    this.isModified = true;
802                    var5.setExtBlockMetadata(par1, par2 & 15, par3, par4);
803                    int var7 = var5.getExtBlockID(par1, par2 & 15, par3);
804    
805                    if (var7 > 0 && Block.blocksList[var7] != null && Block.blocksList[var7].hasTileEntity(par4))
806                    {
807                        TileEntity var8 = this.getChunkBlockTileEntity(par1, par2, par3);
808    
809                        if (var8 != null)
810                        {
811                            var8.updateContainingBlockInfo();
812                            var8.blockMetadata = par4;
813                        }
814                    }
815    
816                    return true;
817                }
818            }
819        }
820    
821        /**
822         * Gets the amount of light saved in this block (doesn't adjust for daylight)
823         */
824        public int getSavedLightValue(EnumSkyBlock par1EnumSkyBlock, int par2, int par3, int par4)
825        {
826            ExtendedBlockStorage var5 = (par3 >> 4 >= storageArrays.length || par3 >> 4 < 0 ? null : storageArrays[par3 >> 4]);
827            return var5 == null ? (this.canBlockSeeTheSky(par2, par3, par4) ? par1EnumSkyBlock.defaultLightValue : 0) : (par1EnumSkyBlock == EnumSkyBlock.Sky ? var5.getExtSkylightValue(par2, par3 & 15, par4) : (par1EnumSkyBlock == EnumSkyBlock.Block ? var5.getExtBlocklightValue(par2, par3 & 15, par4) : par1EnumSkyBlock.defaultLightValue));
828        }
829    
830        /**
831         * Sets the light value at the coordinate. If enumskyblock is set to sky it sets it in the skylightmap and if its a
832         * block then into the blocklightmap. Args enumSkyBlock, x, y, z, lightValue
833         */
834        public void setLightValue(EnumSkyBlock par1EnumSkyBlock, int par2, int par3, int par4, int par5)
835        {
836            if (par3 >> 4 >= storageArrays.length || par3 >> 4 < 0)
837            {
838                return;
839            }
840    
841            ExtendedBlockStorage var6 = this.storageArrays[par3 >> 4];
842    
843            if (var6 == null)
844            {
845                var6 = this.storageArrays[par3 >> 4] = new ExtendedBlockStorage(par3 >> 4 << 4);
846                this.generateSkylightMap();
847            }
848    
849            this.isModified = true;
850    
851            if (par1EnumSkyBlock == EnumSkyBlock.Sky)
852            {
853                if (!this.worldObj.provider.hasNoSky)
854                {
855                    var6.setExtSkylightValue(par2, par3 & 15, par4, par5);
856                }
857            }
858            else if (par1EnumSkyBlock == EnumSkyBlock.Block)
859            {
860                var6.setExtBlocklightValue(par2, par3 & 15, par4, par5);
861            }
862        }
863    
864        /**
865         * Gets the amount of light on a block taking into account sunlight
866         */
867        public int getBlockLightValue(int par1, int par2, int par3, int par4)
868        {
869            ExtendedBlockStorage var5 = (par2 >> 4 >= storageArrays.length || par2 >> 4 < 0 ? null : storageArrays[par2 >> 4]);
870    
871            if (var5 == null)
872            {
873                return !this.worldObj.provider.hasNoSky && par4 < EnumSkyBlock.Sky.defaultLightValue ? EnumSkyBlock.Sky.defaultLightValue - par4 : 0;
874            }
875            else
876            {
877                int var6 = this.worldObj.provider.hasNoSky ? 0 : var5.getExtSkylightValue(par1, par2 & 15, par3);
878    
879                if (var6 > 0)
880                {
881                    isLit = true;
882                }
883    
884                var6 -= par4;
885                int var7 = var5.getExtBlocklightValue(par1, par2 & 15, par3);
886    
887                if (var7 > var6)
888                {
889                    var6 = var7;
890                }
891    
892                return var6;
893            }
894        }
895    
896        /**
897         * Adds an entity to the chunk. Args: entity
898         */
899        public void addEntity(Entity par1Entity)
900        {
901            this.hasEntities = true;
902            int var2 = MathHelper.floor_double(par1Entity.posX / 16.0D);
903            int var3 = MathHelper.floor_double(par1Entity.posZ / 16.0D);
904    
905            if (var2 != this.xPosition || var3 != this.zPosition)
906            {
907                System.out.println("Wrong location! " + par1Entity);
908                Thread.dumpStack();
909            }
910    
911            int var4 = MathHelper.floor_double(par1Entity.posY / 16.0D);
912    
913            if (var4 < 0)
914            {
915                var4 = 0;
916            }
917    
918            if (var4 >= this.entityLists.length)
919            {
920                var4 = this.entityLists.length - 1;
921            }
922            MinecraftForge.EVENT_BUS.post(new EntityEvent.EnteringChunk(par1Entity, this.xPosition, this.zPosition, par1Entity.chunkCoordX, par1Entity.chunkCoordZ));
923            par1Entity.addedToChunk = true;
924            par1Entity.chunkCoordX = this.xPosition;
925            par1Entity.chunkCoordY = var4;
926            par1Entity.chunkCoordZ = this.zPosition;
927            this.entityLists[var4].add(par1Entity);
928        }
929    
930        /**
931         * removes entity using its y chunk coordinate as its index
932         */
933        public void removeEntity(Entity par1Entity)
934        {
935            this.removeEntityAtIndex(par1Entity, par1Entity.chunkCoordY);
936        }
937    
938        /**
939         * Removes entity at the specified index from the entity array.
940         */
941        public void removeEntityAtIndex(Entity par1Entity, int par2)
942        {
943            if (par2 < 0)
944            {
945                par2 = 0;
946            }
947    
948            if (par2 >= this.entityLists.length)
949            {
950                par2 = this.entityLists.length - 1;
951            }
952    
953            this.entityLists[par2].remove(par1Entity);
954        }
955    
956        /**
957         * Returns whether is not a block above this one blocking sight to the sky (done via checking against the heightmap)
958         */
959        public boolean canBlockSeeTheSky(int par1, int par2, int par3)
960        {
961            return par2 >= this.heightMap[par3 << 4 | par1];
962        }
963    
964        /**
965         * Gets the TileEntity for a given block in this chunk
966         */
967        public TileEntity getChunkBlockTileEntity(int par1, int par2, int par3)
968        {
969            ChunkPosition var4 = new ChunkPosition(par1, par2, par3);
970            TileEntity var5 = (TileEntity)this.chunkTileEntityMap.get(var4);
971    
972            if (var5 != null && var5.isInvalid())
973            {
974                chunkTileEntityMap.remove(var4);
975                var5 = null;
976            }
977    
978            if (var5 == null)
979            {
980                int var6 = this.getBlockID(par1, par2, par3);
981    
982                int meta = this.getBlockMetadata(par1, par2, par3);
983    
984                if (var6 <= 0 || !Block.blocksList[var6].hasTileEntity(meta))
985                {
986                    return null;
987                }
988    
989                if (var5 == null)
990                {
991                    var5 = Block.blocksList[var6].createTileEntity(this.worldObj, meta);
992                    this.worldObj.setBlockTileEntity(this.xPosition * 16 + par1, par2, this.zPosition * 16 + par3, var5);
993                }
994    
995                var5 = (TileEntity)this.chunkTileEntityMap.get(var4);
996            }
997    
998            return var5;
999        }
1000    
1001        /**
1002         * Adds a TileEntity to a chunk
1003         */
1004        public void addTileEntity(TileEntity par1TileEntity)
1005        {
1006            int var2 = par1TileEntity.xCoord - this.xPosition * 16;
1007            int var3 = par1TileEntity.yCoord;
1008            int var4 = par1TileEntity.zCoord - this.zPosition * 16;
1009            this.setChunkBlockTileEntity(var2, var3, var4, par1TileEntity);
1010    
1011            if (this.isChunkLoaded)
1012            {
1013                this.worldObj.addTileEntity(par1TileEntity);
1014            }
1015        }
1016    
1017        /**
1018         * Sets the TileEntity for a given block in this chunk
1019         */
1020        public void setChunkBlockTileEntity(int par1, int par2, int par3, TileEntity par4TileEntity)
1021        {
1022            ChunkPosition var5 = new ChunkPosition(par1, par2, par3);
1023            par4TileEntity.setWorldObj(this.worldObj);
1024            par4TileEntity.xCoord = this.xPosition * 16 + par1;
1025            par4TileEntity.yCoord = par2;
1026            par4TileEntity.zCoord = this.zPosition * 16 + par3;
1027    
1028            Block block = Block.blocksList[getBlockID(par1, par2, par3)];
1029            if (block != null && block.hasTileEntity(getBlockMetadata(par1, par2, par3)))
1030            {
1031                TileEntity old = (TileEntity)chunkTileEntityMap.get(var5);
1032                if (old != null)
1033                {
1034                    old.invalidate();
1035                }
1036                par4TileEntity.validate();
1037                this.chunkTileEntityMap.put(var5, par4TileEntity);
1038            }
1039        }
1040    
1041        /**
1042         * Removes the TileEntity for a given block in this chunk
1043         */
1044        public void removeChunkBlockTileEntity(int par1, int par2, int par3)
1045        {
1046            ChunkPosition var4 = new ChunkPosition(par1, par2, par3);
1047    
1048            if (this.isChunkLoaded)
1049            {
1050                TileEntity var5 = (TileEntity)this.chunkTileEntityMap.remove(var4);
1051    
1052                if (var5 != null)
1053                {
1054                    var5.invalidate();
1055                }
1056            }
1057        }
1058    
1059        /**
1060         * Called when this Chunk is loaded by the ChunkProvider
1061         */
1062        public void onChunkLoad()
1063        {
1064            this.isChunkLoaded = true;
1065            this.worldObj.addTileEntity(this.chunkTileEntityMap.values());
1066    
1067            for (int var1 = 0; var1 < this.entityLists.length; ++var1)
1068            {
1069                this.worldObj.addLoadedEntities(this.entityLists[var1]);
1070            }
1071            MinecraftForge.EVENT_BUS.post(new ChunkEvent.Load(this));
1072        }
1073    
1074        /**
1075         * Called when this Chunk is unloaded by the ChunkProvider
1076         */
1077        public void onChunkUnload()
1078        {
1079            this.isChunkLoaded = false;
1080            Iterator var1 = this.chunkTileEntityMap.values().iterator();
1081    
1082            while (var1.hasNext())
1083            {
1084                TileEntity var2 = (TileEntity)var1.next();
1085                this.worldObj.markTileEntityForDespawn(var2);
1086            }
1087    
1088            for (int var3 = 0; var3 < this.entityLists.length; ++var3)
1089            {
1090                this.worldObj.unloadEntities(this.entityLists[var3]);
1091            }
1092            MinecraftForge.EVENT_BUS.post(new ChunkEvent.Unload(this));
1093        }
1094    
1095        /**
1096         * Sets the isModified flag for this Chunk
1097         */
1098        public void setChunkModified()
1099        {
1100            this.isModified = true;
1101        }
1102    
1103        /**
1104         * Fills the given list of all entities that intersect within the given bounding box that aren't the passed entity
1105         * Args: entity, aabb, listToFill
1106         */
1107        public void getEntitiesWithinAABBForEntity(Entity par1Entity, AxisAlignedBB par2AxisAlignedBB, List par3List)
1108        {
1109            int var4 = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
1110            int var5 = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
1111    
1112            if (var4 < 0)
1113            {
1114                var4 = 0;
1115            }
1116    
1117            if (var5 >= this.entityLists.length)
1118            {
1119                var5 = this.entityLists.length - 1;
1120            }
1121    
1122            for (int var6 = var4; var6 <= var5; ++var6)
1123            {
1124                List var7 = this.entityLists[var6];
1125    
1126                for (int var8 = 0; var8 < var7.size(); ++var8)
1127                {
1128                    Entity var9 = (Entity)var7.get(var8);
1129    
1130                    if (var9 != par1Entity && var9.boundingBox.intersectsWith(par2AxisAlignedBB))
1131                    {
1132                        par3List.add(var9);
1133                        Entity[] var10 = var9.getParts();
1134    
1135                        if (var10 != null)
1136                        {
1137                            for (int var11 = 0; var11 < var10.length; ++var11)
1138                            {
1139                                var9 = var10[var11];
1140    
1141                                if (var9 != par1Entity && var9.boundingBox.intersectsWith(par2AxisAlignedBB))
1142                                {
1143                                    par3List.add(var9);
1144                                }
1145                            }
1146                        }
1147                    }
1148                }
1149            }
1150        }
1151    
1152        /**
1153         * Gets all entities that can be assigned to the specified class. Args: entityClass, aabb, listToFill
1154         */
1155        public void getEntitiesOfTypeWithinAAAB(Class par1Class, AxisAlignedBB par2AxisAlignedBB, List par3List, IEntitySelector par4IEntitySelector)
1156        {
1157            int var5 = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
1158            int var6 = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
1159    
1160            if (var5 < 0)
1161            {
1162                var5 = 0;
1163            }
1164            else if (var5 >= this.entityLists.length)
1165            {
1166                var5 = this.entityLists.length - 1;
1167            }
1168    
1169            if (var6 >= this.entityLists.length)
1170            {
1171                var6 = this.entityLists.length - 1;
1172            }
1173            else if (var6 < 0)
1174            {
1175                var6 = 0;
1176            }
1177    
1178            for (int var7 = var5; var7 <= var6; ++var7)
1179            {
1180                List var8 = this.entityLists[var7];
1181    
1182                for (int var9 = 0; var9 < var8.size(); ++var9)
1183                {
1184                    Entity var10 = (Entity)var8.get(var9);
1185    
1186                    if (par1Class.isAssignableFrom(var10.getClass()) && var10.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(var10)))
1187                    {
1188                        par3List.add(var10);
1189                    }
1190                }
1191            }
1192        }
1193    
1194        /**
1195         * Returns true if this Chunk needs to be saved
1196         */
1197        public boolean needsSaving(boolean par1)
1198        {
1199            if (par1)
1200            {
1201                if (this.hasEntities && this.worldObj.getTotalWorldTime() != this.lastSaveTime)
1202                {
1203                    return true;
1204                }
1205            }
1206            else if (this.hasEntities && this.worldObj.getTotalWorldTime() >= this.lastSaveTime + 600L)
1207            {
1208                return true;
1209            }
1210    
1211            return this.isModified;
1212        }
1213    
1214        public Random getRandomWithSeed(long par1)
1215        {
1216            return new Random(this.worldObj.getSeed() + (long)(this.xPosition * this.xPosition * 4987142) + (long)(this.xPosition * 5947611) + (long)(this.zPosition * this.zPosition) * 4392871L + (long)(this.zPosition * 389711) ^ par1);
1217        }
1218    
1219        public boolean isEmpty()
1220        {
1221            return false;
1222        }
1223    
1224        public void populateChunk(IChunkProvider par1IChunkProvider, IChunkProvider par2IChunkProvider, int par3, int par4)
1225        {
1226            if (!this.isTerrainPopulated && par1IChunkProvider.chunkExists(par3 + 1, par4 + 1) && par1IChunkProvider.chunkExists(par3, par4 + 1) && par1IChunkProvider.chunkExists(par3 + 1, par4))
1227            {
1228                par1IChunkProvider.populate(par2IChunkProvider, par3, par4);
1229            }
1230    
1231            if (par1IChunkProvider.chunkExists(par3 - 1, par4) && !par1IChunkProvider.provideChunk(par3 - 1, par4).isTerrainPopulated && par1IChunkProvider.chunkExists(par3 - 1, par4 + 1) && par1IChunkProvider.chunkExists(par3, par4 + 1) && par1IChunkProvider.chunkExists(par3 - 1, par4 + 1))
1232            {
1233                par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4);
1234            }
1235    
1236            if (par1IChunkProvider.chunkExists(par3, par4 - 1) && !par1IChunkProvider.provideChunk(par3, par4 - 1).isTerrainPopulated && par1IChunkProvider.chunkExists(par3 + 1, par4 - 1) && par1IChunkProvider.chunkExists(par3 + 1, par4 - 1) && par1IChunkProvider.chunkExists(par3 + 1, par4))
1237            {
1238                par1IChunkProvider.populate(par2IChunkProvider, par3, par4 - 1);
1239            }
1240    
1241            if (par1IChunkProvider.chunkExists(par3 - 1, par4 - 1) && !par1IChunkProvider.provideChunk(par3 - 1, par4 - 1).isTerrainPopulated && par1IChunkProvider.chunkExists(par3, par4 - 1) && par1IChunkProvider.chunkExists(par3 - 1, par4))
1242            {
1243                par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4 - 1);
1244            }
1245        }
1246    
1247        /**
1248         * Gets the height to which rain/snow will fall. Calculates it if not already stored.
1249         */
1250        public int getPrecipitationHeight(int par1, int par2)
1251        {
1252            int var3 = par1 | par2 << 4;
1253            int var4 = this.precipitationHeightMap[var3];
1254    
1255            if (var4 == -999)
1256            {
1257                int var5 = this.getTopFilledSegment() + 15;
1258                var4 = -1;
1259    
1260                while (var5 > 0 && var4 == -1)
1261                {
1262                    int var6 = this.getBlockID(par1, var5, par2);
1263                    Material var7 = var6 == 0 ? Material.air : Block.blocksList[var6].blockMaterial;
1264    
1265                    if (!var7.blocksMovement() && !var7.isLiquid())
1266                    {
1267                        --var5;
1268                    }
1269                    else
1270                    {
1271                        var4 = var5 + 1;
1272                    }
1273                }
1274    
1275                this.precipitationHeightMap[var3] = var4;
1276            }
1277    
1278            return var4;
1279        }
1280    
1281        /**
1282         * Checks whether skylight needs updated; if it does, calls updateSkylight_do
1283         */
1284        public void updateSkylight()
1285        {
1286            if (this.isGapLightingUpdated && !this.worldObj.provider.hasNoSky)
1287            {
1288                this.updateSkylight_do();
1289            }
1290        }
1291    
1292        /**
1293         * Gets a ChunkCoordIntPair representing the Chunk's position.
1294         */
1295        public ChunkCoordIntPair getChunkCoordIntPair()
1296        {
1297            return new ChunkCoordIntPair(this.xPosition, this.zPosition);
1298        }
1299    
1300        /**
1301         * Returns whether the ExtendedBlockStorages containing levels (in blocks) from arg 1 to arg 2 are fully empty
1302         * (true) or not (false).
1303         */
1304        public boolean getAreLevelsEmpty(int par1, int par2)
1305        {
1306            if (par1 < 0)
1307            {
1308                par1 = 0;
1309            }
1310    
1311            if (par2 >= 256)
1312            {
1313                par2 = 255;
1314            }
1315    
1316            for (int var3 = par1; var3 <= par2; var3 += 16)
1317            {
1318                ExtendedBlockStorage var4 = this.storageArrays[var3 >> 4];
1319    
1320                if (var4 != null && !var4.isEmpty())
1321                {
1322                    return false;
1323                }
1324            }
1325    
1326            return true;
1327        }
1328    
1329        public void setStorageArrays(ExtendedBlockStorage[] par1ArrayOfExtendedBlockStorage)
1330        {
1331            this.storageArrays = par1ArrayOfExtendedBlockStorage;
1332        }
1333    
1334        @SideOnly(Side.CLIENT)
1335    
1336        /**
1337         * Initialise this chunk with new binary data
1338         */
1339        public void fillChunk(byte[] par1ArrayOfByte, int par2, int par3, boolean par4)
1340        {
1341            Iterator iterator = chunkTileEntityMap.values().iterator();
1342            while(iterator.hasNext())
1343            {
1344                TileEntity tileEntity = (TileEntity)iterator.next();
1345                tileEntity.updateContainingBlockInfo();
1346                tileEntity.getBlockMetadata();
1347                tileEntity.getBlockType();
1348            }
1349    
1350            int var5 = 0;
1351            int var6;
1352    
1353            for (var6 = 0; var6 < this.storageArrays.length; ++var6)
1354            {
1355                if ((par2 & 1 << var6) != 0)
1356                {
1357                    if (this.storageArrays[var6] == null)
1358                    {
1359                        this.storageArrays[var6] = new ExtendedBlockStorage(var6 << 4);
1360                    }
1361    
1362                    byte[] var7 = this.storageArrays[var6].getBlockLSBArray();
1363                    System.arraycopy(par1ArrayOfByte, var5, var7, 0, var7.length);
1364                    var5 += var7.length;
1365                }
1366                else if (par4 && this.storageArrays[var6] != null)
1367                {
1368                    this.storageArrays[var6] = null;
1369                }
1370            }
1371    
1372            NibbleArray var8;
1373    
1374            for (var6 = 0; var6 < this.storageArrays.length; ++var6)
1375            {
1376                if ((par2 & 1 << var6) != 0 && this.storageArrays[var6] != null)
1377                {
1378                    var8 = this.storageArrays[var6].getMetadataArray();
1379                    System.arraycopy(par1ArrayOfByte, var5, var8.data, 0, var8.data.length);
1380                    var5 += var8.data.length;
1381                }
1382            }
1383    
1384            for (var6 = 0; var6 < this.storageArrays.length; ++var6)
1385            {
1386                if ((par2 & 1 << var6) != 0 && this.storageArrays[var6] != null)
1387                {
1388                    var8 = this.storageArrays[var6].getBlocklightArray();
1389                    System.arraycopy(par1ArrayOfByte, var5, var8.data, 0, var8.data.length);
1390                    var5 += var8.data.length;
1391                }
1392            }
1393    
1394            for (var6 = 0; var6 < this.storageArrays.length; ++var6)
1395            {
1396                if ((par2 & 1 << var6) != 0 && this.storageArrays[var6] != null)
1397                {
1398                    var8 = this.storageArrays[var6].getSkylightArray();
1399                    System.arraycopy(par1ArrayOfByte, var5, var8.data, 0, var8.data.length);
1400                    var5 += var8.data.length;
1401                }
1402            }
1403    
1404            for (var6 = 0; var6 < this.storageArrays.length; ++var6)
1405            {
1406                if ((par3 & 1 << var6) != 0)
1407                {
1408                    if (this.storageArrays[var6] == null)
1409                    {
1410                        var5 += 2048;
1411                    }
1412                    else
1413                    {
1414                        var8 = this.storageArrays[var6].getBlockMSBArray();
1415    
1416                        if (var8 == null)
1417                        {
1418                            var8 = this.storageArrays[var6].createBlockMSBArray();
1419                        }
1420    
1421                        System.arraycopy(par1ArrayOfByte, var5, var8.data, 0, var8.data.length);
1422                        var5 += var8.data.length;
1423                    }
1424                }
1425                else if (par4 && this.storageArrays[var6] != null && this.storageArrays[var6].getBlockMSBArray() != null)
1426                {
1427                    this.storageArrays[var6].clearMSBArray();
1428                }
1429            }
1430    
1431            if (par4)
1432            {
1433                System.arraycopy(par1ArrayOfByte, var5, this.blockBiomeArray, 0, this.blockBiomeArray.length);
1434                int var10000 = var5 + this.blockBiomeArray.length;
1435            }
1436    
1437            for (var6 = 0; var6 < this.storageArrays.length; ++var6)
1438            {
1439                if (this.storageArrays[var6] != null && (par2 & 1 << var6) != 0)
1440                {
1441                    this.storageArrays[var6].removeInvalidBlocks();
1442                }
1443            }
1444    
1445            this.generateHeightMap();
1446    
1447            List<TileEntity> invalidList = new ArrayList<TileEntity>();
1448            iterator = chunkTileEntityMap.values().iterator();
1449            while (iterator.hasNext())
1450            {
1451                TileEntity tileEntity = (TileEntity)iterator.next();
1452                int x = tileEntity.xCoord & 15;
1453                int y = tileEntity.yCoord;
1454                int z = tileEntity.zCoord & 15;
1455                Block block = tileEntity.getBlockType();
1456                if (block == null || block.blockID != getBlockID(x, y, z) || tileEntity.getBlockMetadata() != getBlockMetadata(x, y, z))
1457                {
1458                    invalidList.add(tileEntity);
1459                }
1460                tileEntity.updateContainingBlockInfo();
1461            }
1462    
1463            for (TileEntity tileEntity : invalidList)
1464            {
1465                tileEntity.invalidate();
1466            }
1467        }
1468    
1469        /**
1470         * This method retrieves the biome at a set of coordinates
1471         */
1472        public BiomeGenBase getBiomeGenForWorldCoords(int par1, int par2, WorldChunkManager par3WorldChunkManager)
1473        {
1474            int var4 = this.blockBiomeArray[par2 << 4 | par1] & 255;
1475    
1476            if (var4 == 255)
1477            {
1478                BiomeGenBase var5 = par3WorldChunkManager.getBiomeGenAt((this.xPosition << 4) + par1, (this.zPosition << 4) + par2);
1479                var4 = var5.biomeID;
1480                this.blockBiomeArray[par2 << 4 | par1] = (byte)(var4 & 255);
1481            }
1482    
1483            return BiomeGenBase.biomeList[var4] == null ? BiomeGenBase.plains : BiomeGenBase.biomeList[var4];
1484        }
1485    
1486        /**
1487         * Returns an array containing a 16x16 mapping on the X/Z of block positions in this Chunk to biome IDs.
1488         */
1489        public byte[] getBiomeArray()
1490        {
1491            return this.blockBiomeArray;
1492        }
1493    
1494        /**
1495         * Accepts a 256-entry array that contains a 16x16 mapping on the X/Z plane of block positions in this Chunk to
1496         * biome IDs.
1497         */
1498        public void setBiomeArray(byte[] par1ArrayOfByte)
1499        {
1500            this.blockBiomeArray = par1ArrayOfByte;
1501        }
1502    
1503        /**
1504         * Resets the relight check index to 0 for this Chunk.
1505         */
1506        public void resetRelightChecks()
1507        {
1508            this.queuedLightChecks = 0;
1509        }
1510    
1511        /**
1512         * Called once-per-chunk-per-tick, and advances the round-robin relight check index per-storage-block by up to 8
1513         * blocks at a time. In a worst-case scenario, can potentially take up to 1.6 seconds, calculated via
1514         * (4096/(8*16))/20, to re-check all blocks in a chunk, which could explain both lagging light updates in certain
1515         * cases as well as Nether relight
1516         */
1517        public void enqueueRelightChecks()
1518        {
1519            for (int var1 = 0; var1 < 8; ++var1)
1520            {
1521                if (this.queuedLightChecks >= 4096)
1522                {
1523                    return;
1524                }
1525    
1526                int var2 = this.queuedLightChecks % 16;
1527                int var3 = this.queuedLightChecks / 16 % 16;
1528                int var4 = this.queuedLightChecks / 256;
1529                ++this.queuedLightChecks;
1530                int var5 = (this.xPosition << 4) + var3;
1531                int var6 = (this.zPosition << 4) + var4;
1532    
1533                for (int var7 = 0; var7 < 16; ++var7)
1534                {
1535                    int var8 = (var2 << 4) + var7;
1536    
1537                    if (this.storageArrays[var2] == null && (var7 == 0 || var7 == 15 || var3 == 0 || var3 == 15 || var4 == 0 || var4 == 15) || this.storageArrays[var2] != null && this.storageArrays[var2].getExtBlockID(var3, var7, var4) == 0)
1538                    {
1539                        if (Block.lightValue[this.worldObj.getBlockId(var5, var8 - 1, var6)] > 0)
1540                        {
1541                            this.worldObj.updateAllLightTypes(var5, var8 - 1, var6);
1542                        }
1543    
1544                        if (Block.lightValue[this.worldObj.getBlockId(var5, var8 + 1, var6)] > 0)
1545                        {
1546                            this.worldObj.updateAllLightTypes(var5, var8 + 1, var6);
1547                        }
1548    
1549                        if (Block.lightValue[this.worldObj.getBlockId(var5 - 1, var8, var6)] > 0)
1550                        {
1551                            this.worldObj.updateAllLightTypes(var5 - 1, var8, var6);
1552                        }
1553    
1554                        if (Block.lightValue[this.worldObj.getBlockId(var5 + 1, var8, var6)] > 0)
1555                        {
1556                            this.worldObj.updateAllLightTypes(var5 + 1, var8, var6);
1557                        }
1558    
1559                        if (Block.lightValue[this.worldObj.getBlockId(var5, var8, var6 - 1)] > 0)
1560                        {
1561                            this.worldObj.updateAllLightTypes(var5, var8, var6 - 1);
1562                        }
1563    
1564                        if (Block.lightValue[this.worldObj.getBlockId(var5, var8, var6 + 1)] > 0)
1565                        {
1566                            this.worldObj.updateAllLightTypes(var5, var8, var6 + 1);
1567                        }
1568    
1569                        this.worldObj.updateAllLightTypes(var5, var8, var6);
1570                    }
1571                }
1572            }
1573        }
1574    
1575        /** FORGE: Used to remove only invalid TileEntities */
1576        public void cleanChunkBlockTileEntity(int x, int y, int z)
1577        {
1578            ChunkPosition position = new ChunkPosition(x, y, z);
1579            if (isChunkLoaded)
1580            {
1581                TileEntity entity = (TileEntity)chunkTileEntityMap.get(position);
1582                if (entity != null && entity.isInvalid())
1583                {
1584                    chunkTileEntityMap.remove(position);
1585                }
1586            }
1587        }
1588    }