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