001package net.minecraft.world.chunk;
002
003import cpw.mods.fml.relauncher.Side;
004import cpw.mods.fml.relauncher.SideOnly;
005import java.util.ArrayList;
006import java.util.Arrays;
007import java.util.HashMap;
008import java.util.Iterator;
009import java.util.List;
010import java.util.Map;
011import java.util.Random;
012import net.minecraft.block.Block;
013import net.minecraft.block.BlockContainer;
014import net.minecraft.block.material.Material;
015import net.minecraft.command.IEntitySelector;
016import net.minecraft.entity.Entity;
017import net.minecraft.tileentity.TileEntity;
018import net.minecraft.util.AxisAlignedBB;
019import net.minecraft.util.MathHelper;
020import net.minecraft.world.ChunkCoordIntPair;
021import net.minecraft.world.ChunkPosition;
022import net.minecraft.world.EnumSkyBlock;
023import net.minecraft.world.World;
024import net.minecraft.world.biome.BiomeGenBase;
025import net.minecraft.world.biome.WorldChunkManager;
026import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
027
028import net.minecraftforge.common.MinecraftForge;
029import net.minecraftforge.event.entity.EntityEvent;
030import net.minecraftforge.event.world.ChunkEvent;
031
032public 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        else if (var4 >= this.entityLists.length) //BugFix: Collision above the world
1132        {
1133            var4 = this.entityLists.length - 1;
1134        }
1135
1136        if (var5 >= this.entityLists.length)
1137        {
1138            var5 = this.entityLists.length - 1;
1139        }
1140        else if (var5 < 0) //BugFix: Collision below the world
1141        {
1142            var5 = 0;
1143        }
1144
1145        for (int var6 = var4; var6 <= var5; ++var6)
1146        {
1147            List var7 = this.entityLists[var6];
1148
1149            for (int var8 = 0; var8 < var7.size(); ++var8)
1150            {
1151                Entity var9 = (Entity)var7.get(var8);
1152
1153                if (var9 != par1Entity && var9.boundingBox.intersectsWith(par2AxisAlignedBB))
1154                {
1155                    par3List.add(var9);
1156                    Entity[] var10 = var9.getParts();
1157
1158                    if (var10 != null)
1159                    {
1160                        for (int var11 = 0; var11 < var10.length; ++var11)
1161                        {
1162                            var9 = var10[var11];
1163
1164                            if (var9 != par1Entity && var9.boundingBox.intersectsWith(par2AxisAlignedBB))
1165                            {
1166                                par3List.add(var9);
1167                            }
1168                        }
1169                    }
1170                }
1171            }
1172        }
1173    }
1174
1175    /**
1176     * Gets all entities that can be assigned to the specified class. Args: entityClass, aabb, listToFill
1177     */
1178    public void getEntitiesOfTypeWithinAAAB(Class par1Class, AxisAlignedBB par2AxisAlignedBB, List par3List, IEntitySelector par4IEntitySelector)
1179    {
1180        int var5 = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
1181        int var6 = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
1182
1183        if (var5 < 0)
1184        {
1185            var5 = 0;
1186        }
1187        else if (var5 >= this.entityLists.length)
1188        {
1189            var5 = this.entityLists.length - 1;
1190        }
1191
1192        if (var6 >= this.entityLists.length)
1193        {
1194            var6 = this.entityLists.length - 1;
1195        }
1196        else if (var6 < 0)
1197        {
1198            var6 = 0;
1199        }
1200
1201        for (int var7 = var5; var7 <= var6; ++var7)
1202        {
1203            List var8 = this.entityLists[var7];
1204
1205            for (int var9 = 0; var9 < var8.size(); ++var9)
1206            {
1207                Entity var10 = (Entity)var8.get(var9);
1208
1209                if (par1Class.isAssignableFrom(var10.getClass()) && var10.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(var10)))
1210                {
1211                    par3List.add(var10);
1212                }
1213            }
1214        }
1215    }
1216
1217    /**
1218     * Returns true if this Chunk needs to be saved
1219     */
1220    public boolean needsSaving(boolean par1)
1221    {
1222        if (par1)
1223        {
1224            if (this.hasEntities && this.worldObj.getTotalWorldTime() != this.lastSaveTime)
1225            {
1226                return true;
1227            }
1228        }
1229        else if (this.hasEntities && this.worldObj.getTotalWorldTime() >= this.lastSaveTime + 600L)
1230        {
1231            return true;
1232        }
1233
1234        return this.isModified;
1235    }
1236
1237    public Random getRandomWithSeed(long par1)
1238    {
1239        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);
1240    }
1241
1242    public boolean isEmpty()
1243    {
1244        return false;
1245    }
1246
1247    public void populateChunk(IChunkProvider par1IChunkProvider, IChunkProvider par2IChunkProvider, int par3, int par4)
1248    {
1249        if (!this.isTerrainPopulated && par1IChunkProvider.chunkExists(par3 + 1, par4 + 1) && par1IChunkProvider.chunkExists(par3, par4 + 1) && par1IChunkProvider.chunkExists(par3 + 1, par4))
1250        {
1251            par1IChunkProvider.populate(par2IChunkProvider, par3, par4);
1252        }
1253
1254        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))
1255        {
1256            par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4);
1257        }
1258
1259        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))
1260        {
1261            par1IChunkProvider.populate(par2IChunkProvider, par3, par4 - 1);
1262        }
1263
1264        if (par1IChunkProvider.chunkExists(par3 - 1, par4 - 1) && !par1IChunkProvider.provideChunk(par3 - 1, par4 - 1).isTerrainPopulated && par1IChunkProvider.chunkExists(par3, par4 - 1) && par1IChunkProvider.chunkExists(par3 - 1, par4))
1265        {
1266            par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4 - 1);
1267        }
1268    }
1269
1270    /**
1271     * Gets the height to which rain/snow will fall. Calculates it if not already stored.
1272     */
1273    public int getPrecipitationHeight(int par1, int par2)
1274    {
1275        int var3 = par1 | par2 << 4;
1276        int var4 = this.precipitationHeightMap[var3];
1277
1278        if (var4 == -999)
1279        {
1280            int var5 = this.getTopFilledSegment() + 15;
1281            var4 = -1;
1282
1283            while (var5 > 0 && var4 == -1)
1284            {
1285                int var6 = this.getBlockID(par1, var5, par2);
1286                Material var7 = var6 == 0 ? Material.air : Block.blocksList[var6].blockMaterial;
1287
1288                if (!var7.blocksMovement() && !var7.isLiquid())
1289                {
1290                    --var5;
1291                }
1292                else
1293                {
1294                    var4 = var5 + 1;
1295                }
1296            }
1297
1298            this.precipitationHeightMap[var3] = var4;
1299        }
1300
1301        return var4;
1302    }
1303
1304    /**
1305     * Checks whether skylight needs updated; if it does, calls updateSkylight_do
1306     */
1307    public void updateSkylight()
1308    {
1309        if (this.isGapLightingUpdated && !this.worldObj.provider.hasNoSky)
1310        {
1311            this.updateSkylight_do();
1312        }
1313    }
1314
1315    /**
1316     * Gets a ChunkCoordIntPair representing the Chunk's position.
1317     */
1318    public ChunkCoordIntPair getChunkCoordIntPair()
1319    {
1320        return new ChunkCoordIntPair(this.xPosition, this.zPosition);
1321    }
1322
1323    /**
1324     * Returns whether the ExtendedBlockStorages containing levels (in blocks) from arg 1 to arg 2 are fully empty
1325     * (true) or not (false).
1326     */
1327    public boolean getAreLevelsEmpty(int par1, int par2)
1328    {
1329        if (par1 < 0)
1330        {
1331            par1 = 0;
1332        }
1333
1334        if (par2 >= 256)
1335        {
1336            par2 = 255;
1337        }
1338
1339        for (int var3 = par1; var3 <= par2; var3 += 16)
1340        {
1341            ExtendedBlockStorage var4 = this.storageArrays[var3 >> 4];
1342
1343            if (var4 != null && !var4.isEmpty())
1344            {
1345                return false;
1346            }
1347        }
1348
1349        return true;
1350    }
1351
1352    public void setStorageArrays(ExtendedBlockStorage[] par1ArrayOfExtendedBlockStorage)
1353    {
1354        this.storageArrays = par1ArrayOfExtendedBlockStorage;
1355    }
1356
1357    @SideOnly(Side.CLIENT)
1358
1359    /**
1360     * Initialise this chunk with new binary data
1361     */
1362    public void fillChunk(byte[] par1ArrayOfByte, int par2, int par3, boolean par4)
1363    {
1364        Iterator iterator = chunkTileEntityMap.values().iterator();
1365        while(iterator.hasNext())
1366        {
1367            TileEntity tileEntity = (TileEntity)iterator.next();
1368            tileEntity.updateContainingBlockInfo();
1369            tileEntity.getBlockMetadata();
1370            tileEntity.getBlockType();
1371        }
1372
1373        int var5 = 0;
1374        boolean var6 = !this.worldObj.provider.hasNoSky;
1375        int var7;
1376
1377        for (var7 = 0; var7 < this.storageArrays.length; ++var7)
1378        {
1379            if ((par2 & 1 << var7) != 0)
1380            {
1381                if (this.storageArrays[var7] == null)
1382                {
1383                    this.storageArrays[var7] = new ExtendedBlockStorage(var7 << 4, var6);
1384                }
1385
1386                byte[] var8 = this.storageArrays[var7].getBlockLSBArray();
1387                System.arraycopy(par1ArrayOfByte, var5, var8, 0, var8.length);
1388                var5 += var8.length;
1389            }
1390            else if (par4 && this.storageArrays[var7] != null)
1391            {
1392                this.storageArrays[var7] = null;
1393            }
1394        }
1395
1396        NibbleArray var9;
1397
1398        for (var7 = 0; var7 < this.storageArrays.length; ++var7)
1399        {
1400            if ((par2 & 1 << var7) != 0 && this.storageArrays[var7] != null)
1401            {
1402                var9 = this.storageArrays[var7].getMetadataArray();
1403                System.arraycopy(par1ArrayOfByte, var5, var9.data, 0, var9.data.length);
1404                var5 += var9.data.length;
1405            }
1406        }
1407
1408        for (var7 = 0; var7 < this.storageArrays.length; ++var7)
1409        {
1410            if ((par2 & 1 << var7) != 0 && this.storageArrays[var7] != null)
1411            {
1412                var9 = this.storageArrays[var7].getBlocklightArray();
1413                System.arraycopy(par1ArrayOfByte, var5, var9.data, 0, var9.data.length);
1414                var5 += var9.data.length;
1415            }
1416        }
1417
1418        if (var6)
1419        {
1420            for (var7 = 0; var7 < this.storageArrays.length; ++var7)
1421            {
1422                if ((par2 & 1 << var7) != 0 && this.storageArrays[var7] != null)
1423                {
1424                    var9 = this.storageArrays[var7].getSkylightArray();
1425                    System.arraycopy(par1ArrayOfByte, var5, var9.data, 0, var9.data.length);
1426                    var5 += var9.data.length;
1427                }
1428            }
1429        }
1430
1431        for (var7 = 0; var7 < this.storageArrays.length; ++var7)
1432        {
1433            if ((par3 & 1 << var7) != 0)
1434            {
1435                if (this.storageArrays[var7] == null)
1436                {
1437                    var5 += 2048;
1438                }
1439                else
1440                {
1441                    var9 = this.storageArrays[var7].getBlockMSBArray();
1442
1443                    if (var9 == null)
1444                    {
1445                        var9 = this.storageArrays[var7].createBlockMSBArray();
1446                    }
1447
1448                    System.arraycopy(par1ArrayOfByte, var5, var9.data, 0, var9.data.length);
1449                    var5 += var9.data.length;
1450                }
1451            }
1452            else if (par4 && this.storageArrays[var7] != null && this.storageArrays[var7].getBlockMSBArray() != null)
1453            {
1454                this.storageArrays[var7].clearMSBArray();
1455            }
1456        }
1457
1458        if (par4)
1459        {
1460            System.arraycopy(par1ArrayOfByte, var5, this.blockBiomeArray, 0, this.blockBiomeArray.length);
1461            int var10000 = var5 + this.blockBiomeArray.length;
1462        }
1463
1464        for (var7 = 0; var7 < this.storageArrays.length; ++var7)
1465        {
1466            if (this.storageArrays[var7] != null && (par2 & 1 << var7) != 0)
1467            {
1468                this.storageArrays[var7].removeInvalidBlocks();
1469            }
1470        }
1471
1472        this.generateHeightMap();
1473
1474        List<TileEntity> invalidList = new ArrayList<TileEntity>();
1475        iterator = chunkTileEntityMap.values().iterator();
1476        while (iterator.hasNext())
1477        {
1478            TileEntity tileEntity = (TileEntity)iterator.next();
1479            int x = tileEntity.xCoord & 15;
1480            int y = tileEntity.yCoord;
1481            int z = tileEntity.zCoord & 15;
1482            Block block = tileEntity.getBlockType();
1483            if (block == null || block.blockID != getBlockID(x, y, z) || tileEntity.getBlockMetadata() != getBlockMetadata(x, y, z))
1484            {
1485                invalidList.add(tileEntity);
1486            }
1487            tileEntity.updateContainingBlockInfo();
1488        }
1489
1490        for (TileEntity tileEntity : invalidList)
1491        {
1492            tileEntity.invalidate();
1493        }
1494    }
1495
1496    /**
1497     * This method retrieves the biome at a set of coordinates
1498     */
1499    public BiomeGenBase getBiomeGenForWorldCoords(int par1, int par2, WorldChunkManager par3WorldChunkManager)
1500    {
1501        int var4 = this.blockBiomeArray[par2 << 4 | par1] & 255;
1502
1503        if (var4 == 255)
1504        {
1505            BiomeGenBase var5 = par3WorldChunkManager.getBiomeGenAt((this.xPosition << 4) + par1, (this.zPosition << 4) + par2);
1506            var4 = var5.biomeID;
1507            this.blockBiomeArray[par2 << 4 | par1] = (byte)(var4 & 255);
1508        }
1509
1510        return BiomeGenBase.biomeList[var4] == null ? BiomeGenBase.plains : BiomeGenBase.biomeList[var4];
1511    }
1512
1513    /**
1514     * Returns an array containing a 16x16 mapping on the X/Z of block positions in this Chunk to biome IDs.
1515     */
1516    public byte[] getBiomeArray()
1517    {
1518        return this.blockBiomeArray;
1519    }
1520
1521    /**
1522     * Accepts a 256-entry array that contains a 16x16 mapping on the X/Z plane of block positions in this Chunk to
1523     * biome IDs.
1524     */
1525    public void setBiomeArray(byte[] par1ArrayOfByte)
1526    {
1527        this.blockBiomeArray = par1ArrayOfByte;
1528    }
1529
1530    /**
1531     * Resets the relight check index to 0 for this Chunk.
1532     */
1533    public void resetRelightChecks()
1534    {
1535        this.queuedLightChecks = 0;
1536    }
1537
1538    /**
1539     * Called once-per-chunk-per-tick, and advances the round-robin relight check index per-storage-block by up to 8
1540     * blocks at a time. In a worst-case scenario, can potentially take up to 1.6 seconds, calculated via
1541     * (4096/(8*16))/20, to re-check all blocks in a chunk, which could explain both lagging light updates in certain
1542     * cases as well as Nether relight
1543     */
1544    public void enqueueRelightChecks()
1545    {
1546        for (int var1 = 0; var1 < 8; ++var1)
1547        {
1548            if (this.queuedLightChecks >= 4096)
1549            {
1550                return;
1551            }
1552
1553            int var2 = this.queuedLightChecks % 16;
1554            int var3 = this.queuedLightChecks / 16 % 16;
1555            int var4 = this.queuedLightChecks / 256;
1556            ++this.queuedLightChecks;
1557            int var5 = (this.xPosition << 4) + var3;
1558            int var6 = (this.zPosition << 4) + var4;
1559
1560            for (int var7 = 0; var7 < 16; ++var7)
1561            {
1562                int var8 = (var2 << 4) + var7;
1563
1564                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)
1565                {
1566                    if (Block.lightValue[this.worldObj.getBlockId(var5, var8 - 1, var6)] > 0)
1567                    {
1568                        this.worldObj.updateAllLightTypes(var5, var8 - 1, var6);
1569                    }
1570
1571                    if (Block.lightValue[this.worldObj.getBlockId(var5, var8 + 1, var6)] > 0)
1572                    {
1573                        this.worldObj.updateAllLightTypes(var5, var8 + 1, var6);
1574                    }
1575
1576                    if (Block.lightValue[this.worldObj.getBlockId(var5 - 1, var8, var6)] > 0)
1577                    {
1578                        this.worldObj.updateAllLightTypes(var5 - 1, var8, var6);
1579                    }
1580
1581                    if (Block.lightValue[this.worldObj.getBlockId(var5 + 1, var8, var6)] > 0)
1582                    {
1583                        this.worldObj.updateAllLightTypes(var5 + 1, var8, var6);
1584                    }
1585
1586                    if (Block.lightValue[this.worldObj.getBlockId(var5, var8, var6 - 1)] > 0)
1587                    {
1588                        this.worldObj.updateAllLightTypes(var5, var8, var6 - 1);
1589                    }
1590
1591                    if (Block.lightValue[this.worldObj.getBlockId(var5, var8, var6 + 1)] > 0)
1592                    {
1593                        this.worldObj.updateAllLightTypes(var5, var8, var6 + 1);
1594                    }
1595
1596                    this.worldObj.updateAllLightTypes(var5, var8, var6);
1597                }
1598            }
1599        }
1600    }
1601
1602    /** FORGE: Used to remove only invalid TileEntities */
1603    public void cleanChunkBlockTileEntity(int x, int y, int z)
1604    {
1605        ChunkPosition position = new ChunkPosition(x, y, z);
1606        if (isChunkLoaded)
1607        {
1608            TileEntity entity = (TileEntity)chunkTileEntityMap.get(position);
1609            if (entity != null && entity.isInvalid())
1610            {
1611                chunkTileEntityMap.remove(position);
1612            }
1613        }
1614    }
1615}