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