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)
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)
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            ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
688            boolean flag = false;
689
690            if (extendedblockstorage == null)
691            {
692                if (par4 == 0)
693                {
694                    return false;
695                }
696
697                extendedblockstorage = this.storageArrays[par2 >> 4] = new ExtendedBlockStorage(par2 >> 4 << 4, !this.worldObj.provider.hasNoSky);
698                flag = par2 >= k1;
699            }
700
701            int j2 = this.xPosition * 16 + par1;
702            int k2 = this.zPosition * 16 + par3;
703
704            if (l1 != 0 && !this.worldObj.isRemote)
705            {
706                Block.blocksList[l1].onSetBlockIDWithMetaData(this.worldObj, j2, par2, k2, i2);
707            }
708
709            extendedblockstorage.setExtBlockID(par1, par2 & 15, par3, par4);
710
711            if (l1 != 0)
712            {
713                if (!this.worldObj.isRemote)
714                {
715                    Block.blocksList[l1].breakBlock(this.worldObj, j2, par2, k2, l1, i2);
716                }
717                else if (Block.blocksList[l1] != null && Block.blocksList[l1].hasTileEntity(i2))
718                {
719                    TileEntity te = worldObj.getBlockTileEntity(j2, par2, k2);
720                    if (te != null && te.shouldRefresh(l1, par4, i2, par5, worldObj, j2, par2, k2))
721                    {
722                        this.worldObj.removeBlockTileEntity(j2, par2, k2);
723                    }
724                }
725            }
726
727            if (extendedblockstorage.getExtBlockID(par1, par2 & 15, par3) != par4)
728            {
729                return false;
730            }
731            else
732            {
733                extendedblockstorage.setExtBlockMetadata(par1, par2 & 15, par3, par5);
734
735                if (flag)
736                {
737                    this.generateSkylightMap();
738                }
739                else
740                {
741                    if (getBlockLightOpacity(par1, par2, par3) > 0)
742                    {
743                        if (par2 >= k1)
744                        {
745                            this.relightBlock(par1, par2 + 1, par3);
746                        }
747                    }
748                    else if (par2 == k1 - 1)
749                    {
750                        this.relightBlock(par1, par2, par3);
751                    }
752
753                    this.propagateSkylightOcclusion(par1, par3);
754                }
755
756                TileEntity tileentity;
757
758                if (par4 != 0)
759                {
760                    if (!this.worldObj.isRemote)
761                    {
762                        Block.blocksList[par4].onBlockAdded(this.worldObj, j2, par2, k2);
763                    }
764
765                    if (Block.blocksList[par4] != null && Block.blocksList[par4].hasTileEntity(par5))
766                    {
767                        tileentity = this.getChunkBlockTileEntity(par1, par2, par3);
768
769                        if (tileentity == null)
770                        {
771                            tileentity = Block.blocksList[par4].createTileEntity(this.worldObj, par5);
772                            this.worldObj.setBlockTileEntity(j2, par2, k2, tileentity);
773                        }
774
775                        if (tileentity != null)
776                        {
777                            tileentity.updateContainingBlockInfo();
778                            tileentity.blockMetadata = par5;
779                        }
780                    }
781                }
782
783                this.isModified = true;
784                return true;
785            }
786        }
787    }
788
789    /**
790     * Set the metadata of a block in the chunk
791     */
792    public boolean setBlockMetadata(int par1, int par2, int par3, int par4)
793    {
794        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
795
796        if (extendedblockstorage == null)
797        {
798            return false;
799        }
800        else
801        {
802            int i1 = extendedblockstorage.getExtBlockMetadata(par1, par2 & 15, par3);
803
804            if (i1 == par4)
805            {
806                return false;
807            }
808            else
809            {
810                this.isModified = true;
811                extendedblockstorage.setExtBlockMetadata(par1, par2 & 15, par3, par4);
812                int j1 = extendedblockstorage.getExtBlockID(par1, par2 & 15, par3);
813
814                if (j1 > 0 && Block.blocksList[j1] != null && Block.blocksList[j1].hasTileEntity(par4))
815                {
816                    TileEntity tileentity = this.getChunkBlockTileEntity(par1, par2, par3);
817
818                    if (tileentity != null)
819                    {
820                        tileentity.updateContainingBlockInfo();
821                        tileentity.blockMetadata = par4;
822                    }
823                }
824
825                return true;
826            }
827        }
828    }
829
830    /**
831     * Gets the amount of light saved in this block (doesn't adjust for daylight)
832     */
833    public int getSavedLightValue(EnumSkyBlock par1EnumSkyBlock, int par2, int par3, int par4)
834    {
835        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par3 >> 4];
836        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));
837    }
838
839    /**
840     * Sets the light value at the coordinate. If enumskyblock is set to sky it sets it in the skylightmap and if its a
841     * block then into the blocklightmap. Args enumSkyBlock, x, y, z, lightValue
842     */
843    public void setLightValue(EnumSkyBlock par1EnumSkyBlock, int par2, int par3, int par4, int par5)
844    {
845        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par3 >> 4];
846
847        if (extendedblockstorage == null)
848        {
849            extendedblockstorage = this.storageArrays[par3 >> 4] = new ExtendedBlockStorage(par3 >> 4 << 4, !this.worldObj.provider.hasNoSky);
850            this.generateSkylightMap();
851        }
852
853        this.isModified = true;
854
855        if (par1EnumSkyBlock == EnumSkyBlock.Sky)
856        {
857            if (!this.worldObj.provider.hasNoSky)
858            {
859                extendedblockstorage.setExtSkylightValue(par2, par3 & 15, par4, par5);
860            }
861        }
862        else if (par1EnumSkyBlock == EnumSkyBlock.Block)
863        {
864            extendedblockstorage.setExtBlocklightValue(par2, par3 & 15, par4, par5);
865        }
866    }
867
868    /**
869     * Gets the amount of light on a block taking into account sunlight
870     */
871    public int getBlockLightValue(int par1, int par2, int par3, int par4)
872    {
873        ExtendedBlockStorage extendedblockstorage = this.storageArrays[par2 >> 4];
874
875        if (extendedblockstorage == null)
876        {
877            return !this.worldObj.provider.hasNoSky && par4 < EnumSkyBlock.Sky.defaultLightValue ? EnumSkyBlock.Sky.defaultLightValue - par4 : 0;
878        }
879        else
880        {
881            int i1 = this.worldObj.provider.hasNoSky ? 0 : extendedblockstorage.getExtSkylightValue(par1, par2 & 15, par3);
882
883            if (i1 > 0)
884            {
885                isLit = true;
886            }
887
888            i1 -= par4;
889            int j1 = extendedblockstorage.getExtBlocklightValue(par1, par2 & 15, par3);
890
891            if (j1 > i1)
892            {
893                i1 = j1;
894            }
895
896            return i1;
897        }
898    }
899
900    /**
901     * Adds an entity to the chunk. Args: entity
902     */
903    public void addEntity(Entity par1Entity)
904    {
905        this.hasEntities = true;
906        int i = MathHelper.floor_double(par1Entity.posX / 16.0D);
907        int j = MathHelper.floor_double(par1Entity.posZ / 16.0D);
908
909        if (i != this.xPosition || j != this.zPosition)
910        {
911            this.worldObj.getWorldLogAgent().logSevere("Wrong location! " + par1Entity);
912            Thread.dumpStack();
913        }
914
915        int k = MathHelper.floor_double(par1Entity.posY / 16.0D);
916
917        if (k < 0)
918        {
919            k = 0;
920        }
921
922        if (k >= this.entityLists.length)
923        {
924            k = this.entityLists.length - 1;
925        }
926
927        MinecraftForge.EVENT_BUS.post(new EntityEvent.EnteringChunk(par1Entity, this.xPosition, this.zPosition, par1Entity.chunkCoordX, par1Entity.chunkCoordZ));
928        par1Entity.addedToChunk = true;
929        par1Entity.chunkCoordX = this.xPosition;
930        par1Entity.chunkCoordY = k;
931        par1Entity.chunkCoordZ = this.zPosition;
932        this.entityLists[k].add(par1Entity);
933    }
934
935    /**
936     * removes entity using its y chunk coordinate as its index
937     */
938    public void removeEntity(Entity par1Entity)
939    {
940        this.removeEntityAtIndex(par1Entity, par1Entity.chunkCoordY);
941    }
942
943    /**
944     * Removes entity at the specified index from the entity array.
945     */
946    public void removeEntityAtIndex(Entity par1Entity, int par2)
947    {
948        if (par2 < 0)
949        {
950            par2 = 0;
951        }
952
953        if (par2 >= this.entityLists.length)
954        {
955            par2 = this.entityLists.length - 1;
956        }
957
958        this.entityLists[par2].remove(par1Entity);
959    }
960
961    /**
962     * Returns whether is not a block above this one blocking sight to the sky (done via checking against the heightmap)
963     */
964    public boolean canBlockSeeTheSky(int par1, int par2, int par3)
965    {
966        return par2 >= this.heightMap[par3 << 4 | par1];
967    }
968
969    /**
970     * Gets the TileEntity for a given block in this chunk
971     */
972    public TileEntity getChunkBlockTileEntity(int par1, int par2, int par3)
973    {
974        ChunkPosition chunkposition = new ChunkPosition(par1, par2, par3);
975        TileEntity tileentity = (TileEntity)this.chunkTileEntityMap.get(chunkposition);
976
977        if (tileentity != null && tileentity.isInvalid())
978        {
979            chunkTileEntityMap.remove(chunkposition);
980            tileentity = null;
981        }
982
983        if (tileentity == null)
984        {
985            int l = this.getBlockID(par1, par2, par3);
986            int meta = this.getBlockMetadata(par1, par2, par3);
987
988            if (l <= 0 || !Block.blocksList[l].hasTileEntity(meta))
989            {
990                return null;
991            }
992
993            if (tileentity == null)
994            {
995                tileentity = Block.blocksList[l].createTileEntity(this.worldObj, meta);
996                this.worldObj.setBlockTileEntity(this.xPosition * 16 + par1, par2, this.zPosition * 16 + par3, tileentity);
997            }
998
999            tileentity = (TileEntity)this.chunkTileEntityMap.get(chunkposition);
1000        }
1001
1002        return tileentity;
1003    }
1004
1005    /**
1006     * Adds a TileEntity to a chunk
1007     */
1008    public void addTileEntity(TileEntity par1TileEntity)
1009    {
1010        int i = par1TileEntity.xCoord - this.xPosition * 16;
1011        int j = par1TileEntity.yCoord;
1012        int k = par1TileEntity.zCoord - this.zPosition * 16;
1013        this.setChunkBlockTileEntity(i, j, k, par1TileEntity);
1014
1015        if (this.isChunkLoaded)
1016        {
1017            this.worldObj.addTileEntity(par1TileEntity);
1018        }
1019    }
1020
1021    /**
1022     * Sets the TileEntity for a given block in this chunk
1023     */
1024    public void setChunkBlockTileEntity(int par1, int par2, int par3, TileEntity par4TileEntity)
1025    {
1026        ChunkPosition chunkposition = new ChunkPosition(par1, par2, par3);
1027        par4TileEntity.setWorldObj(this.worldObj);
1028        par4TileEntity.xCoord = this.xPosition * 16 + par1;
1029        par4TileEntity.yCoord = par2;
1030        par4TileEntity.zCoord = this.zPosition * 16 + par3;
1031
1032        Block block = Block.blocksList[getBlockID(par1, par2, par3)];
1033        if (block != null && block.hasTileEntity(getBlockMetadata(par1, par2, par3)))
1034        {
1035            if (this.chunkTileEntityMap.containsKey(chunkposition))
1036            {
1037                ((TileEntity)this.chunkTileEntityMap.get(chunkposition)).invalidate();
1038            }
1039
1040            par4TileEntity.validate();
1041            this.chunkTileEntityMap.put(chunkposition, par4TileEntity);
1042        }
1043    }
1044
1045    /**
1046     * Removes the TileEntity for a given block in this chunk
1047     */
1048    public void removeChunkBlockTileEntity(int par1, int par2, int par3)
1049    {
1050        ChunkPosition chunkposition = new ChunkPosition(par1, par2, par3);
1051
1052        if (this.isChunkLoaded)
1053        {
1054            TileEntity tileentity = (TileEntity)this.chunkTileEntityMap.remove(chunkposition);
1055
1056            if (tileentity != null)
1057            {
1058                tileentity.invalidate();
1059            }
1060        }
1061    }
1062
1063    /**
1064     * Called when this Chunk is loaded by the ChunkProvider
1065     */
1066    public void onChunkLoad()
1067    {
1068        this.isChunkLoaded = true;
1069        this.worldObj.addTileEntity(this.chunkTileEntityMap.values());
1070
1071        for (int i = 0; i < this.entityLists.length; ++i)
1072        {
1073            this.worldObj.addLoadedEntities(this.entityLists[i]);
1074        }
1075        MinecraftForge.EVENT_BUS.post(new ChunkEvent.Load(this));
1076    }
1077
1078    /**
1079     * Called when this Chunk is unloaded by the ChunkProvider
1080     */
1081    public void onChunkUnload()
1082    {
1083        this.isChunkLoaded = false;
1084        Iterator iterator = this.chunkTileEntityMap.values().iterator();
1085
1086        while (iterator.hasNext())
1087        {
1088            TileEntity tileentity = (TileEntity)iterator.next();
1089            this.worldObj.markTileEntityForDespawn(tileentity);
1090        }
1091
1092        for (int i = 0; i < this.entityLists.length; ++i)
1093        {
1094            this.worldObj.unloadEntities(this.entityLists[i]);
1095        }
1096        MinecraftForge.EVENT_BUS.post(new ChunkEvent.Unload(this));
1097    }
1098
1099    /**
1100     * Sets the isModified flag for this Chunk
1101     */
1102    public void setChunkModified()
1103    {
1104        this.isModified = true;
1105    }
1106
1107    /**
1108     * Fills the given list of all entities that intersect within the given bounding box that aren't the passed entity
1109     * Args: entity, aabb, listToFill
1110     */
1111    public void getEntitiesWithinAABBForEntity(Entity par1Entity, AxisAlignedBB par2AxisAlignedBB, List par3List, IEntitySelector par4IEntitySelector)
1112    {
1113        int i = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
1114        int j = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
1115
1116        if (i < 0)
1117        {
1118            i = 0;
1119            j = Math.max(i, j);
1120        }
1121
1122        if (j >= this.entityLists.length)
1123        {
1124            j = this.entityLists.length - 1;
1125            i = Math.min(i, j);
1126        }
1127
1128        for (int k = i; k <= j; ++k)
1129        {
1130            List list1 = this.entityLists[k];
1131
1132            for (int l = 0; l < list1.size(); ++l)
1133            {
1134                Entity entity1 = (Entity)list1.get(l);
1135
1136                if (entity1 != par1Entity && entity1.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(entity1)))
1137                {
1138                    par3List.add(entity1);
1139                    Entity[] aentity = entity1.getParts();
1140
1141                    if (aentity != null)
1142                    {
1143                        for (int i1 = 0; i1 < aentity.length; ++i1)
1144                        {
1145                            entity1 = aentity[i1];
1146
1147                            if (entity1 != par1Entity && entity1.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(entity1)))
1148                            {
1149                                par3List.add(entity1);
1150                            }
1151                        }
1152                    }
1153                }
1154            }
1155        }
1156    }
1157
1158    /**
1159     * Gets all entities that can be assigned to the specified class. Args: entityClass, aabb, listToFill
1160     */
1161    public void getEntitiesOfTypeWithinAAAB(Class par1Class, AxisAlignedBB par2AxisAlignedBB, List par3List, IEntitySelector par4IEntitySelector)
1162    {
1163        int i = MathHelper.floor_double((par2AxisAlignedBB.minY - World.MAX_ENTITY_RADIUS) / 16.0D);
1164        int j = MathHelper.floor_double((par2AxisAlignedBB.maxY + World.MAX_ENTITY_RADIUS) / 16.0D);
1165
1166        if (i < 0)
1167        {
1168            i = 0;
1169        }
1170        else if (i >= this.entityLists.length)
1171        {
1172            i = this.entityLists.length - 1;
1173        }
1174
1175        if (j >= this.entityLists.length)
1176        {
1177            j = this.entityLists.length - 1;
1178        }
1179        else if (j < 0)
1180        {
1181            j = 0;
1182        }
1183
1184        for (int k = i; k <= j; ++k)
1185        {
1186            List list1 = this.entityLists[k];
1187
1188            for (int l = 0; l < list1.size(); ++l)
1189            {
1190                Entity entity = (Entity)list1.get(l);
1191
1192                if (par1Class.isAssignableFrom(entity.getClass()) && entity.boundingBox.intersectsWith(par2AxisAlignedBB) && (par4IEntitySelector == null || par4IEntitySelector.isEntityApplicable(entity)))
1193                {
1194                    par3List.add(entity);
1195                }
1196            }
1197        }
1198    }
1199
1200    /**
1201     * Returns true if this Chunk needs to be saved
1202     */
1203    public boolean needsSaving(boolean par1)
1204    {
1205        if (par1)
1206        {
1207            if (this.hasEntities && this.worldObj.getTotalWorldTime() != this.lastSaveTime || this.isModified)
1208            {
1209                return true;
1210            }
1211        }
1212        else if (this.hasEntities && this.worldObj.getTotalWorldTime() >= this.lastSaveTime + 600L)
1213        {
1214            return true;
1215        }
1216
1217        return this.isModified;
1218    }
1219
1220    public Random getRandomWithSeed(long par1)
1221    {
1222        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);
1223    }
1224
1225    public boolean isEmpty()
1226    {
1227        return false;
1228    }
1229
1230    public void populateChunk(IChunkProvider par1IChunkProvider, IChunkProvider par2IChunkProvider, int par3, int par4)
1231    {
1232        if (!this.isTerrainPopulated && par1IChunkProvider.chunkExists(par3 + 1, par4 + 1) && par1IChunkProvider.chunkExists(par3, par4 + 1) && par1IChunkProvider.chunkExists(par3 + 1, par4))
1233        {
1234            par1IChunkProvider.populate(par2IChunkProvider, par3, par4);
1235        }
1236
1237        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))
1238        {
1239            par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4);
1240        }
1241
1242        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))
1243        {
1244            par1IChunkProvider.populate(par2IChunkProvider, par3, par4 - 1);
1245        }
1246
1247        if (par1IChunkProvider.chunkExists(par3 - 1, par4 - 1) && !par1IChunkProvider.provideChunk(par3 - 1, par4 - 1).isTerrainPopulated && par1IChunkProvider.chunkExists(par3, par4 - 1) && par1IChunkProvider.chunkExists(par3 - 1, par4))
1248        {
1249            par1IChunkProvider.populate(par2IChunkProvider, par3 - 1, par4 - 1);
1250        }
1251    }
1252
1253    /**
1254     * Gets the height to which rain/snow will fall. Calculates it if not already stored.
1255     */
1256    public int getPrecipitationHeight(int par1, int par2)
1257    {
1258        int k = par1 | par2 << 4;
1259        int l = this.precipitationHeightMap[k];
1260
1261        if (l == -999)
1262        {
1263            int i1 = this.getTopFilledSegment() + 15;
1264            l = -1;
1265
1266            while (i1 > 0 && l == -1)
1267            {
1268                int j1 = this.getBlockID(par1, i1, par2);
1269                Material material = j1 == 0 ? Material.air : Block.blocksList[j1].blockMaterial;
1270
1271                if (!material.blocksMovement() && !material.isLiquid())
1272                {
1273                    --i1;
1274                }
1275                else
1276                {
1277                    l = i1 + 1;
1278                }
1279            }
1280
1281            this.precipitationHeightMap[k] = l;
1282        }
1283
1284        return l;
1285    }
1286
1287    /**
1288     * Checks whether skylight needs updated; if it does, calls updateSkylight_do
1289     */
1290    public void updateSkylight()
1291    {
1292        if (this.isGapLightingUpdated && !this.worldObj.provider.hasNoSky)
1293        {
1294            this.updateSkylight_do();
1295        }
1296    }
1297
1298    /**
1299     * Gets a ChunkCoordIntPair representing the Chunk's position.
1300     */
1301    public ChunkCoordIntPair getChunkCoordIntPair()
1302    {
1303        return new ChunkCoordIntPair(this.xPosition, this.zPosition);
1304    }
1305
1306    /**
1307     * Returns whether the ExtendedBlockStorages containing levels (in blocks) from arg 1 to arg 2 are fully empty
1308     * (true) or not (false).
1309     */
1310    public boolean getAreLevelsEmpty(int par1, int par2)
1311    {
1312        if (par1 < 0)
1313        {
1314            par1 = 0;
1315        }
1316
1317        if (par2 >= 256)
1318        {
1319            par2 = 255;
1320        }
1321
1322        for (int k = par1; k <= par2; k += 16)
1323        {
1324            ExtendedBlockStorage extendedblockstorage = this.storageArrays[k >> 4];
1325
1326            if (extendedblockstorage != null && !extendedblockstorage.isEmpty())
1327            {
1328                return false;
1329            }
1330        }
1331
1332        return true;
1333    }
1334
1335    public void setStorageArrays(ExtendedBlockStorage[] par1ArrayOfExtendedBlockStorage)
1336    {
1337        this.storageArrays = par1ArrayOfExtendedBlockStorage;
1338    }
1339
1340    @SideOnly(Side.CLIENT)
1341
1342    /**
1343     * Initialise this chunk with new binary data
1344     */
1345    public void fillChunk(byte[] par1ArrayOfByte, int par2, int par3, boolean par4)
1346    {
1347        Iterator iterator = chunkTileEntityMap.values().iterator();
1348        while(iterator.hasNext())
1349        {
1350            TileEntity tileEntity = (TileEntity)iterator.next();
1351            tileEntity.updateContainingBlockInfo();
1352            tileEntity.getBlockMetadata();
1353            tileEntity.getBlockType();
1354        }
1355
1356        int k = 0;
1357        boolean flag1 = !this.worldObj.provider.hasNoSky;
1358        int l;
1359
1360        for (l = 0; l < this.storageArrays.length; ++l)
1361        {
1362            if ((par2 & 1 << l) != 0)
1363            {
1364                if (this.storageArrays[l] == null)
1365                {
1366                    this.storageArrays[l] = new ExtendedBlockStorage(l << 4, flag1);
1367                }
1368
1369                byte[] abyte1 = this.storageArrays[l].getBlockLSBArray();
1370                System.arraycopy(par1ArrayOfByte, k, abyte1, 0, abyte1.length);
1371                k += abyte1.length;
1372            }
1373            else if (par4 && this.storageArrays[l] != null)
1374            {
1375                this.storageArrays[l] = null;
1376            }
1377        }
1378
1379        NibbleArray nibblearray;
1380
1381        for (l = 0; l < this.storageArrays.length; ++l)
1382        {
1383            if ((par2 & 1 << l) != 0 && this.storageArrays[l] != null)
1384            {
1385                nibblearray = this.storageArrays[l].getMetadataArray();
1386                System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
1387                k += nibblearray.data.length;
1388            }
1389        }
1390
1391        for (l = 0; l < this.storageArrays.length; ++l)
1392        {
1393            if ((par2 & 1 << l) != 0 && this.storageArrays[l] != null)
1394            {
1395                nibblearray = this.storageArrays[l].getBlocklightArray();
1396                System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
1397                k += nibblearray.data.length;
1398            }
1399        }
1400
1401        if (flag1)
1402        {
1403            for (l = 0; l < this.storageArrays.length; ++l)
1404            {
1405                if ((par2 & 1 << l) != 0 && this.storageArrays[l] != null)
1406                {
1407                    nibblearray = this.storageArrays[l].getSkylightArray();
1408                    System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
1409                    k += nibblearray.data.length;
1410                }
1411            }
1412        }
1413
1414        for (l = 0; l < this.storageArrays.length; ++l)
1415        {
1416            if ((par3 & 1 << l) != 0)
1417            {
1418                if (this.storageArrays[l] == null)
1419                {
1420                    k += 2048;
1421                }
1422                else
1423                {
1424                    nibblearray = this.storageArrays[l].getBlockMSBArray();
1425
1426                    if (nibblearray == null)
1427                    {
1428                        nibblearray = this.storageArrays[l].createBlockMSBArray();
1429                    }
1430
1431                    System.arraycopy(par1ArrayOfByte, k, nibblearray.data, 0, nibblearray.data.length);
1432                    k += nibblearray.data.length;
1433                }
1434            }
1435            else if (par4 && this.storageArrays[l] != null && this.storageArrays[l].getBlockMSBArray() != null)
1436            {
1437                this.storageArrays[l].clearMSBArray();
1438            }
1439        }
1440
1441        if (par4)
1442        {
1443            System.arraycopy(par1ArrayOfByte, k, this.blockBiomeArray, 0, this.blockBiomeArray.length);
1444            int i1 = k + this.blockBiomeArray.length;
1445        }
1446
1447        for (l = 0; l < this.storageArrays.length; ++l)
1448        {
1449            if (this.storageArrays[l] != null && (par2 & 1 << l) != 0)
1450            {
1451                this.storageArrays[l].removeInvalidBlocks();
1452            }
1453        }
1454
1455        this.generateHeightMap();
1456
1457        List<TileEntity> invalidList = new ArrayList<TileEntity>();
1458        iterator = chunkTileEntityMap.values().iterator();
1459        while (iterator.hasNext())
1460        {
1461            TileEntity tileEntity = (TileEntity)iterator.next();
1462            int x = tileEntity.xCoord & 15;
1463            int y = tileEntity.yCoord;
1464            int z = tileEntity.zCoord & 15;
1465            Block block = tileEntity.getBlockType();
1466            if (block == null || block.blockID != getBlockID(x, y, z) || tileEntity.getBlockMetadata() != getBlockMetadata(x, y, z))
1467            {
1468                invalidList.add(tileEntity);
1469            }
1470            tileEntity.updateContainingBlockInfo();
1471        }
1472
1473        for (TileEntity tileEntity : invalidList)
1474        {
1475            tileEntity.invalidate();
1476        }
1477    }
1478
1479    /**
1480     * This method retrieves the biome at a set of coordinates
1481     */
1482    public BiomeGenBase getBiomeGenForWorldCoords(int par1, int par2, WorldChunkManager par3WorldChunkManager)
1483    {
1484        int k = this.blockBiomeArray[par2 << 4 | par1] & 255;
1485
1486        if (k == 255)
1487        {
1488            BiomeGenBase biomegenbase = par3WorldChunkManager.getBiomeGenAt((this.xPosition << 4) + par1, (this.zPosition << 4) + par2);
1489            k = biomegenbase.biomeID;
1490            this.blockBiomeArray[par2 << 4 | par1] = (byte)(k & 255);
1491        }
1492
1493        return BiomeGenBase.biomeList[k] == null ? BiomeGenBase.plains : BiomeGenBase.biomeList[k];
1494    }
1495
1496    /**
1497     * Returns an array containing a 16x16 mapping on the X/Z of block positions in this Chunk to biome IDs.
1498     */
1499    public byte[] getBiomeArray()
1500    {
1501        return this.blockBiomeArray;
1502    }
1503
1504    /**
1505     * Accepts a 256-entry array that contains a 16x16 mapping on the X/Z plane of block positions in this Chunk to
1506     * biome IDs.
1507     */
1508    public void setBiomeArray(byte[] par1ArrayOfByte)
1509    {
1510        this.blockBiomeArray = par1ArrayOfByte;
1511    }
1512
1513    /**
1514     * Resets the relight check index to 0 for this Chunk.
1515     */
1516    public void resetRelightChecks()
1517    {
1518        this.queuedLightChecks = 0;
1519    }
1520
1521    /**
1522     * Called once-per-chunk-per-tick, and advances the round-robin relight check index per-storage-block by up to 8
1523     * blocks at a time. In a worst-case scenario, can potentially take up to 1.6 seconds, calculated via
1524     * (4096/(8*16))/20, to re-check all blocks in a chunk, which could explain both lagging light updates in certain
1525     * cases as well as Nether relight
1526     */
1527    public void enqueueRelightChecks()
1528    {
1529        for (int i = 0; i < 8; ++i)
1530        {
1531            if (this.queuedLightChecks >= 4096)
1532            {
1533                return;
1534            }
1535
1536            int j = this.queuedLightChecks % 16;
1537            int k = this.queuedLightChecks / 16 % 16;
1538            int l = this.queuedLightChecks / 256;
1539            ++this.queuedLightChecks;
1540            int i1 = (this.xPosition << 4) + k;
1541            int j1 = (this.zPosition << 4) + l;
1542
1543            for (int k1 = 0; k1 < 16; ++k1)
1544            {
1545                int l1 = (j << 4) + k1;
1546
1547                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)
1548                {
1549                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1 - 1, j1)] > 0)
1550                    {
1551                        this.worldObj.updateAllLightTypes(i1, l1 - 1, j1);
1552                    }
1553
1554                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1 + 1, j1)] > 0)
1555                    {
1556                        this.worldObj.updateAllLightTypes(i1, l1 + 1, j1);
1557                    }
1558
1559                    if (Block.lightValue[this.worldObj.getBlockId(i1 - 1, l1, j1)] > 0)
1560                    {
1561                        this.worldObj.updateAllLightTypes(i1 - 1, l1, j1);
1562                    }
1563
1564                    if (Block.lightValue[this.worldObj.getBlockId(i1 + 1, l1, j1)] > 0)
1565                    {
1566                        this.worldObj.updateAllLightTypes(i1 + 1, l1, j1);
1567                    }
1568
1569                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1, j1 - 1)] > 0)
1570                    {
1571                        this.worldObj.updateAllLightTypes(i1, l1, j1 - 1);
1572                    }
1573
1574                    if (Block.lightValue[this.worldObj.getBlockId(i1, l1, j1 + 1)] > 0)
1575                    {
1576                        this.worldObj.updateAllLightTypes(i1, l1, j1 + 1);
1577                    }
1578
1579                    this.worldObj.updateAllLightTypes(i1, l1, j1);
1580                }
1581            }
1582        }
1583    }
1584
1585    /** FORGE: Used to remove only invalid TileEntities */
1586    public void cleanChunkBlockTileEntity(int x, int y, int z)
1587    {
1588        ChunkPosition position = new ChunkPosition(x, y, z);
1589        if (isChunkLoaded)
1590        {
1591            TileEntity entity = (TileEntity)chunkTileEntityMap.get(position);
1592            if (entity != null && entity.isInvalid())
1593            {
1594                chunkTileEntityMap.remove(position);
1595            }
1596        }
1597    }
1598}