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